package originclient import ( "context" "encoding/json" "net/http" "net/http/httptest" "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func testConfig(url string) Config { return Config{ URL: url, ServiceKey: "test-key", Type: "icy2", Version: "v1.0.0", Name: "icy2-test", Site: "Test Site", Environment: "test", IntervalHours: 24, InstallID: "test-install-id", } } func TestNew_RequiresFields(t *testing.T) { _, err := New(Config{}) assert.Error(t, err) } func TestNew_AppliesDefaults(t *testing.T) { c, err := New(Config{ServiceKey: "k", Type: "t", Version: "v", Name: "n", InstallID: "id"}) require.NoError(t, err) assert.Equal(t, DefaultURL, c.cfg.URL) assert.Equal(t, "go", c.cfg.AppType) assert.Equal(t, DefaultIntervalHours, c.cfg.IntervalHours) assert.Equal(t, "id", c.installID) } func TestBuildPayload(t *testing.T) { c, err := New(testConfig("https://example.invalid/checkin")) require.NoError(t, err) payload := c.buildPayload() assert.Equal(t, "icy2", payload.Type) assert.Equal(t, "go", payload.AppType) assert.Equal(t, "v1.0.0", payload.Version) assert.Equal(t, "icy2-test", payload.Name) assert.Equal(t, "Test Site", payload.Site) assert.Equal(t, "test", payload.Environment) assert.Equal(t, "test-install-id", payload.UniqueInstallID) } func TestCheckinOnce_Success(t *testing.T) { var gotKey string var gotBody Payload server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotKey = r.Header.Get("X-Origin-Service-Key") require.NoError(t, json.NewDecoder(r.Body).Decode(&gotBody)) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(Response{ Success: true, ServiceInstanceID: 42, UniqueInstallID: gotBody.UniqueInstallID, Status: "provisioning", PingedAt: "2026-08-01T00:00:00Z", }) })) defer server.Close() c, err := New(testConfig(server.URL)) require.NoError(t, err) resp, err := c.CheckinOnce(t.Context()) require.NoError(t, err) assert.Equal(t, "test-key", gotKey) assert.Equal(t, "icy2", gotBody.Type) assert.Equal(t, "icy2-test", gotBody.Name) assert.Equal(t, "test-install-id", gotBody.UniqueInstallID) assert.True(t, resp.Success) assert.Equal(t, int64(42), resp.ServiceInstanceID) } func TestCheckinOnce_ServerErrorReturnsError(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer server.Close() c, err := New(testConfig(server.URL)) require.NoError(t, err) resp, err := c.CheckinOnce(t.Context()) assert.Error(t, err) assert.Nil(t, resp) } func TestCheckinOnce_UnreachableDoesNotPanic(t *testing.T) { c, err := New(testConfig("http://127.0.0.1:1")) require.NoError(t, err) assert.NotPanics(t, func() { _, _ = c.CheckinOnce(t.Context()) }) } func TestCheckinOnce_LogsOnFailure(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer server.Close() cfg := testConfig(server.URL) var warned bool cfg.Logger = LoggerFunc(func(msg string, err error) { warned = true }) c, err := New(cfg) require.NoError(t, err) _, _ = c.CheckinOnce(t.Context()) assert.True(t, warned) } func TestRun_ChecksInImmediatelyThenStopsOnContextDone(t *testing.T) { var calls atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls.Add(1) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(Response{Success: true}) })) defer server.Close() c, err := New(testConfig(server.URL)) require.NoError(t, err) ctx, cancel := context.WithTimeout(t.Context(), 200*time.Millisecond) defer cancel() c.Run(ctx) assert.Equal(t, int32(1), calls.Load()) }