From 936a118834df406fc7964c932544e6202b2abbf4 Mon Sep 17 00:00:00 2001 From: Rob Zolkos Date: Fri, 23 Oct 2026 15:38:00 -0400 Subject: [PATCH] Keep Unix-only connector start tests out of Windows builds --- internal/commands/connect_start_wait_test.go | 91 --------------- .../commands/connect_start_wait_unix_test.go | 108 ++++++++++++++++++ 2 files changed, 108 insertions(+), 91 deletions(-) create mode 100644 internal/commands/connect_start_wait_unix_test.go diff --git a/internal/commands/connect_start_wait_test.go b/internal/commands/connect_start_wait_test.go index 343594bbe..355009cb6 100644 --- a/internal/commands/connect_start_wait_test.go +++ b/internal/commands/connect_start_wait_test.go @@ -25,7 +25,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/basecamp/basecamp-cli/internal/appctx" "github.com/basecamp/basecamp-cli/internal/auth" "github.com/basecamp/basecamp-cli/internal/config" "github.com/basecamp/basecamp-cli/internal/connector" @@ -483,52 +482,6 @@ func TestAnOversizedServerFaultIsWaitedOut(t *testing.T) { assert.Equal(t, 2, mints()) } -// A second connector for an agent that is already running says so at once, -// even while the token endpoint is rate-limiting it: the lock is checked -// before the start waits for a token, not after. -func TestASecondConnectorIsRefusedBeforeItWaitsForAToken(t *testing.T) { - f := newOperatorFixture(t) - dir, err := connectStateDir("agent", f.file, false) - require.NoError(t, err) - lock, err := connector.AcquireInstanceLock(dir, f.file.AccountID, f.file.Agent.PersonID, time.Now()) - require.NoError(t, err) - t.Cleanup(func() { _ = lock.Release() }) - - app := newConnectSetupApp(t, f.s, "agent") - creds, err := app.Auth.GetStore().Load(app.Auth.CredentialKey()) - require.NoError(t, err) - creds.ExpiresAt = time.Now().Add(-time.Minute).Unix() - require.NoError(t, app.Auth.GetStore().Save(app.Auth.CredentialKey(), creds)) - f.s.mu.Lock() - f.s.rateLimitMints, f.s.mints = true, 0 - f.s.mu.Unlock() - - ctx, cancel := context.WithCancel(t.Context()) - defer cancel() - var buf bytes.Buffer - app.Output = output.New(output.Options{Format: output.FormatJSON, Writer: &buf}) - cmd := NewConnectCmd() - cmd.SetArgs(nil) - cmd.SetContext(appctx.WithApp(ctx, app)) - cmd.SetOut(&buf) - cmd.SetErr(&buf) - cmd.SilenceErrors, cmd.SilenceUsage = true, true - done := make(chan error, 1) - go func() { done <- cmd.Execute() }() - - select { - case err := <-done: - assert.Equal(t, output.CodeLockUnavailable, usageError(t, err).Code, buf.String()) - case <-time.After(10 * time.Second): - cancel() - <-done - t.Fatalf("a second connector waited for a token instead of refusing: %s", buf.String()) - } - f.s.mu.Lock() - defer f.s.mu.Unlock() - assert.Zero(t, f.s.mints, "nothing was minted for a connector that cannot run") -} - // A 503 that names its Retry-After is waited out for that long, by the // start and the feed alike, not on backoff. func TestAServerFaultsRetryAfterIsHonored(t *testing.T) { @@ -564,50 +517,6 @@ func (b *lockedBuffer) String() string { return b.buf.String() } -// A start waiting for its token does not hold the agent's lock: until the -// credential is proven to be that agent, the lock is only looked at, so a -// profile whose credential is some other agent's cannot keep the real -// connector out while it waits. -func TestAStartWaitingForItsTokenHoldsNoLock(t *testing.T) { - f := newOperatorFixture(t) - app := newConnectSetupApp(t, f.s, "agent") - creds, err := app.Auth.GetStore().Load(app.Auth.CredentialKey()) - require.NoError(t, err) - creds.ExpiresAt = time.Now().Add(-time.Minute).Unix() - require.NoError(t, app.Auth.GetStore().Save(app.Auth.CredentialKey(), creds)) - f.s.mu.Lock() - f.s.rateLimitMints = true - f.s.mu.Unlock() - - ctx, cancel := context.WithCancel(t.Context()) - defer cancel() - var out lockedBuffer - app.Output = output.New(output.Options{Format: output.FormatJSON, Writer: &out}) - cmd := NewConnectCmd() - cmd.SetArgs(nil) - cmd.SetContext(appctx.WithApp(ctx, app)) - cmd.SetOut(&out) - cmd.SetErr(&out) - cmd.SilenceErrors, cmd.SilenceUsage = true, true - done := make(chan error, 1) - go func() { done <- cmd.Execute() }() - - require.Eventually(t, func() bool { return strings.Contains(out.String(), "could not get the agent's token yet") }, - 10*time.Second, 20*time.Millisecond, "the start never waited: %s", out.String()) - dir, err := connectStateDir("agent", f.file, false) - require.NoError(t, err) - lock, err := connector.AcquireInstanceLock(dir, f.file.AccountID, f.file.Agent.PersonID, time.Now()) - require.NoError(t, err, "a waiting start held the agent's lock") - require.NoError(t, lock.Release()) - - cancel() - select { - case <-done: - case <-time.After(10 * time.Second): - t.Fatal("the waiting start did not stop when canceled") - } -} - // okTokens is a renewal that succeeds at once. type okTokens struct{} diff --git a/internal/commands/connect_start_wait_unix_test.go b/internal/commands/connect_start_wait_unix_test.go new file mode 100644 index 000000000..33a4a178b --- /dev/null +++ b/internal/commands/connect_start_wait_unix_test.go @@ -0,0 +1,108 @@ +//go:build unix + +package commands + +import ( + "bytes" + "context" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/basecamp/basecamp-cli/internal/appctx" + "github.com/basecamp/basecamp-cli/internal/connector" + "github.com/basecamp/basecamp-cli/internal/output" +) + +// A second connector for an agent that is already running says so at once, +// even while the token endpoint is rate-limiting it: the lock is checked +// before the start waits for a token, not after. +func TestASecondConnectorIsRefusedBeforeItWaitsForAToken(t *testing.T) { + f := newOperatorFixture(t) + dir, err := connectStateDir("agent", f.file, false) + require.NoError(t, err) + lock, err := connector.AcquireInstanceLock(dir, f.file.AccountID, f.file.Agent.PersonID, time.Now()) + require.NoError(t, err) + t.Cleanup(func() { _ = lock.Release() }) + + app := newConnectSetupApp(t, f.s, "agent") + creds, err := app.Auth.GetStore().Load(app.Auth.CredentialKey()) + require.NoError(t, err) + creds.ExpiresAt = time.Now().Add(-time.Minute).Unix() + require.NoError(t, app.Auth.GetStore().Save(app.Auth.CredentialKey(), creds)) + f.s.mu.Lock() + f.s.rateLimitMints, f.s.mints = true, 0 + f.s.mu.Unlock() + + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + var buf bytes.Buffer + app.Output = output.New(output.Options{Format: output.FormatJSON, Writer: &buf}) + cmd := NewConnectCmd() + cmd.SetArgs(nil) + cmd.SetContext(appctx.WithApp(ctx, app)) + cmd.SetOut(&buf) + cmd.SetErr(&buf) + cmd.SilenceErrors, cmd.SilenceUsage = true, true + done := make(chan error, 1) + go func() { done <- cmd.Execute() }() + + select { + case err := <-done: + assert.Equal(t, output.CodeLockUnavailable, usageError(t, err).Code, buf.String()) + case <-time.After(10 * time.Second): + cancel() + <-done + t.Fatalf("a second connector waited for a token instead of refusing: %s", buf.String()) + } + f.s.mu.Lock() + defer f.s.mu.Unlock() + assert.Zero(t, f.s.mints, "nothing was minted for a connector that cannot run") +} + +// A start waiting for its token does not hold the agent's lock: until the +// credential is proven to be that agent, the lock is only looked at, so a +// profile whose credential is some other agent's cannot keep the real +// connector out while it waits. +func TestAStartWaitingForItsTokenHoldsNoLock(t *testing.T) { + f := newOperatorFixture(t) + app := newConnectSetupApp(t, f.s, "agent") + creds, err := app.Auth.GetStore().Load(app.Auth.CredentialKey()) + require.NoError(t, err) + creds.ExpiresAt = time.Now().Add(-time.Minute).Unix() + require.NoError(t, app.Auth.GetStore().Save(app.Auth.CredentialKey(), creds)) + f.s.mu.Lock() + f.s.rateLimitMints = true + f.s.mu.Unlock() + + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + var out lockedBuffer + app.Output = output.New(output.Options{Format: output.FormatJSON, Writer: &out}) + cmd := NewConnectCmd() + cmd.SetArgs(nil) + cmd.SetContext(appctx.WithApp(ctx, app)) + cmd.SetOut(&out) + cmd.SetErr(&out) + cmd.SilenceErrors, cmd.SilenceUsage = true, true + done := make(chan error, 1) + go func() { done <- cmd.Execute() }() + + require.Eventually(t, func() bool { return strings.Contains(out.String(), "could not get the agent's token yet") }, + 10*time.Second, 20*time.Millisecond, "the start never waited: %s", out.String()) + dir, err := connectStateDir("agent", f.file, false) + require.NoError(t, err) + lock, err := connector.AcquireInstanceLock(dir, f.file.AccountID, f.file.Agent.PersonID, time.Now()) + require.NoError(t, err, "a waiting start held the agent's lock") + require.NoError(t, lock.Release()) + + cancel() + select { + case <-done: + case <-time.After(10 * time.Second): + t.Fatal("the waiting start did not stop when canceled") + } +}