Skip to content
Draft
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
83 changes: 83 additions & 0 deletions internal/setup/verifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package setup

import (
"encoding/json"
"fmt"
"net/url"
"time"

"github.com/launchdarkly/ldcli/internal/resources"
)

// VerifyResult describes the outcome of verifying SDK connectivity.
type VerifyResult struct {
Active bool `json:"active"`
Attempts int `json:"attempts"`
Elapsed string `json:"elapsed"`
}

// Verifier polls the sdk-active endpoint until the SDK reports as active or a timeout is reached.
type Verifier struct {
Client resources.Client
Interval time.Duration
Timeout time.Duration
}

// DefaultVerifier returns a Verifier with sensible defaults.
func DefaultVerifier(client resources.Client) *Verifier {
return &Verifier{
Client: client,
Interval: 5 * time.Second,
Timeout: 120 * time.Second,
}
}

// Verify polls GET /api/v2/projects/{project}/environments/{env}/sdk-active until active=true.
func (v *Verifier) Verify(accessToken, baseURI, projectKey, envKey string) (*VerifyResult, error) {
start := time.Now()
deadline := start.Add(v.Timeout)
attempts := 0

for {
attempts++
active, err := v.checkOnce(accessToken, baseURI, projectKey, envKey)
if err != nil {
return nil, err
}
if active {
return &VerifyResult{
Active: true,
Attempts: attempts,
Elapsed: time.Since(start).Round(time.Millisecond).String(),
}, nil
}

if time.Now().After(deadline) {
return &VerifyResult{
Active: false,
Attempts: attempts,
Elapsed: time.Since(start).Round(time.Millisecond).String(),
}, nil
}

time.Sleep(v.Interval)
}
}

func (v *Verifier) checkOnce(accessToken, baseURI, projectKey, envKey string) (bool, error) {
path, _ := url.JoinPath(baseURI, "api/v2/projects", projectKey, "environments", envKey, "sdk-active")

res, err := v.Client.MakeRequest(accessToken, "GET", path, "application/json", nil, nil, false)
if err != nil {
return false, fmt.Errorf("checking sdk-active: %w", err)
}

var resp struct {
Active bool `json:"active"`
}
if err := json.Unmarshal(res, &resp); err != nil {
return false, fmt.Errorf("parsing sdk-active response: %w", err)
}

return resp.Active, nil
}
43 changes: 43 additions & 0 deletions internal/setup/verifier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package setup

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/launchdarkly/ldcli/internal/resources"
)

func TestVerify_Active(t *testing.T) {
client := &resources.MockClient{
Response: []byte(`{"active": true}`),
}
verifier := &Verifier{
Client: client,
Interval: 10 * time.Millisecond,
Timeout: 1 * time.Second,
}

result, err := verifier.Verify("token", "https://app.launchdarkly.com", "proj", "env")
require.NoError(t, err)
assert.True(t, result.Active)
assert.Equal(t, 1, result.Attempts)
}

func TestVerify_InactiveTimesOut(t *testing.T) {
client := &resources.MockClient{
Response: []byte(`{"active": false}`),
}
verifier := &Verifier{
Client: client,
Interval: 10 * time.Millisecond,
Timeout: 50 * time.Millisecond,
}

result, err := verifier.Verify("token", "https://app.launchdarkly.com", "proj", "env")
require.NoError(t, err)
assert.False(t, result.Active)
assert.Greater(t, result.Attempts, 1)
}
Loading