Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion config_fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import (
"context"
"errors"
"fmt"
"github.com/configcat/go-sdk/v9/configcatcache"
"io"
"net/http"
"os"
"sync"
"sync/atomic"
"time"

"github.com/configcat/go-sdk/v9/configcatcache"
)

const (
Expand Down Expand Up @@ -209,6 +210,12 @@ func (f *configFetcher) refreshIfOlder(ctx context.Context, before time.Time, wa
func (f *configFetcher) fetcher(prevConfig *config) {
defer f.wg.Done()
config, newURL, err := f.fetchConfig(f.ctx, f.baseURL, prevConfig)

// Call OnConfigDownloaded hook only for HTTP requests (not cache or local-only)
if !f.isOffline() && (f.overrides == nil || f.overrides.Behavior != LocalOnly) {
f.callOnConfigDownloaded(err)
}

f.mu.Lock()
defer f.mu.Unlock()
if err != nil {
Expand Down Expand Up @@ -422,6 +429,13 @@ func (f *configFetcher) fetchHTTPWithoutRedirect(ctx context.Context, baseURL st
return nil, &fetcherError{EventId: 1101, Err: fmt.Errorf("unexpected HTTP response was received while trying to fetch config JSON: %v", response.Status)}
}

// callOnConfigDownloaded calls the OnConfigDownloaded hook if it's configured.
func (f *configFetcher) callOnConfigDownloaded(err error) {
if f.hooks != nil && f.hooks.OnConfigDownloaded != nil {
go f.hooks.OnConfigDownloaded(err)
}
}

func pollingModeToIdentifier(pollingMode PollingMode) string {
switch pollingMode {
case AutoPoll:
Expand Down
3 changes: 3 additions & 0 deletions configcat_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type Hooks struct {

// OnConfigChanged is called, when a new config.json has downloaded.
OnConfigChanged func()

// OnConfigDownloaded is called every time a config download attempt is made.
OnConfigDownloaded func(err error)
}

// Config describes configuration options for the Client.
Expand Down
67 changes: 67 additions & 0 deletions configcat_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,73 @@ func rootNodeWithKeyValueType(key string, value interface{}, t SettingType) *Con
}
}

func TestClient_Hooks_OnConfigDownloaded(t *testing.T) {
tests := []struct {
name string
expectError bool
refreshTwice bool
}{
{
name: "successful_download",
expectError: false,
refreshTwice: false,
},
{
name: "successful_download_with_refresh",
expectError: false,
refreshTwice: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := qt.New(t)
srv := newConfigServer(t)
srv.setResponse(configResponse{
body: contentForIntegrationTestKey("PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A"),
})

downloadedChan := make(chan error, 10)
cfg := srv.config()
cfg.Hooks = &Hooks{OnConfigDownloaded: func(err error) {
downloadedChan <- err
}}
client := NewCustomClient(cfg)
defer client.Close()

// Wait for the initial config download
select {
case err := <-downloadedChan:
if test.expectError {
c.Assert(err, qt.Not(qt.IsNil))
} else {
c.Assert(err, qt.IsNil)
}
case <-time.After(time.Second):
t.Fatalf("timed out waiting for OnConfigDownloaded hook")
}

if test.refreshTwice {
// Trigger a refresh to get another download event
err := client.Refresh(context.Background())
c.Assert(err, qt.IsNil)

// Should get another download notification
select {
case err := <-downloadedChan:
if test.expectError {
c.Assert(err, qt.Not(qt.IsNil))
} else {
c.Assert(err, qt.IsNil)
}
case <-time.After(time.Second):
t.Fatalf("timed out waiting for second OnConfigDownloaded hook")
}
}
})
}
}

type mockHTTPTransport struct {
requests []*http.Request
responses []*http.Response
Expand Down