From 978e6e41ef7d2fda32fb9559567063524e9a190b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 00:50:55 +0000 Subject: [PATCH 1/3] Initial plan From 0d2eb492afedc186c72fd7d28a9ab5ccc7f894c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 01:03:15 +0000 Subject: [PATCH 2/3] Add comprehensive tests to achieve 100% code coverage Co-authored-by: ziflex <1607148+ziflex@users.noreply.github.com> --- helpers_test.go | 60 ++++++++++ options_test.go | 69 +++++++++++ registry_test.go | 115 ++++++++++++++++++ waitfor_test.go | 298 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 542 insertions(+) create mode 100644 helpers_test.go create mode 100644 options_test.go create mode 100644 waitfor_test.go diff --git a/helpers_test.go b/helpers_test.go new file mode 100644 index 0000000..938bb7f --- /dev/null +++ b/helpers_test.go @@ -0,0 +1,60 @@ +package waitfor + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUse(t *testing.T) { + // Create a mock module function + mockModule := func() ([]string, ResourceFactory) { + schemes := []string{"mock", "test"} + factory := func(u *url.URL) (Resource, error) { + return &TestResource{}, nil + } + return schemes, factory + } + + // Test Use function + config := Use(mockModule) + + assert.Equal(t, []string{"mock", "test"}, config.Scheme) + assert.NotNil(t, config.Factory) + + // Test that the factory works + testURL, _ := url.Parse("mock://example") + resource, err := config.Factory(testURL) + assert.NoError(t, err) + assert.NotNil(t, resource) +} + +func TestUse_WithEmptySchemes(t *testing.T) { + // Create a module with empty schemes + mockModule := func() ([]string, ResourceFactory) { + schemes := []string{} + factory := func(u *url.URL) (Resource, error) { + return &TestResource{}, nil + } + return schemes, factory + } + + config := Use(mockModule) + + assert.Empty(t, config.Scheme) + assert.NotNil(t, config.Factory) +} + +func TestUse_WithNilFactory(t *testing.T) { + // Create a module with nil factory + mockModule := func() ([]string, ResourceFactory) { + schemes := []string{"nil"} + return schemes, nil + } + + config := Use(mockModule) + + assert.Equal(t, []string{"nil"}, config.Scheme) + assert.Nil(t, config.Factory) +} \ No newline at end of file diff --git a/options_test.go b/options_test.go new file mode 100644 index 0000000..529aefd --- /dev/null +++ b/options_test.go @@ -0,0 +1,69 @@ +package waitfor + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestNewOptions_Defaults(t *testing.T) { + opts := newOptions([]Option{}) + + assert.Equal(t, time.Duration(5)*time.Second, opts.interval) + assert.Equal(t, time.Duration(60)*time.Second, opts.maxInterval) + assert.Equal(t, uint64(5), opts.attempts) +} + +func TestNewOptions_WithSetters(t *testing.T) { + setters := []Option{ + WithInterval(10), + WithMaxInterval(120), + WithAttempts(15), + } + + opts := newOptions(setters) + + assert.Equal(t, time.Duration(10)*time.Second, opts.interval) + assert.Equal(t, time.Duration(120)*time.Second, opts.maxInterval) + assert.Equal(t, uint64(15), opts.attempts) +} + +func TestWithInterval(t *testing.T) { + option := WithInterval(30) + opts := &Options{} + + option(opts) + + assert.Equal(t, time.Duration(30)*time.Second, opts.interval) +} + +func TestWithMaxInterval(t *testing.T) { + option := WithMaxInterval(90) + opts := &Options{} + + option(opts) + + assert.Equal(t, time.Duration(90)*time.Second, opts.maxInterval) +} + +func TestWithAttempts(t *testing.T) { + option := WithAttempts(20) + opts := &Options{} + + option(opts) + + assert.Equal(t, uint64(20), opts.attempts) +} + +func TestCombinedOptions(t *testing.T) { + opts := newOptions([]Option{ + WithInterval(2), + WithMaxInterval(30), + WithAttempts(8), + }) + + assert.Equal(t, time.Duration(2)*time.Second, opts.interval) + assert.Equal(t, time.Duration(30)*time.Second, opts.maxInterval) + assert.Equal(t, uint64(8), opts.attempts) +} \ No newline at end of file diff --git a/registry_test.go b/registry_test.go index e81fe3b..3ba5bf3 100644 --- a/registry_test.go +++ b/registry_test.go @@ -2,6 +2,7 @@ package waitfor import ( "context" + "errors" "net/url" "testing" @@ -36,3 +37,117 @@ func TestRegistry_Register(t *testing.T) { assert.NoError(t, err) assert.NotNilf(t, rsc, "resource not found") } + +func TestRegistry_Register_NewScheme(t *testing.T) { + r := newRegistry([]ResourceConfig{}) + + factory := func(u *url.URL) (Resource, error) { + return &TestResource{}, nil + } + + err := r.Register("custom", factory) + assert.NoError(t, err) + + // Verify the scheme was registered + rsc, err := r.Resolve("custom://test") + assert.NoError(t, err) + assert.NotNil(t, rsc) +} + +func TestRegistry_Register_DuplicateScheme(t *testing.T) { + factory := func(u *url.URL) (Resource, error) { + return &TestResource{}, nil + } + + r := newRegistry([]ResourceConfig{ + { + Scheme: []string{"existing"}, + Factory: factory, + }, + }) + + // Try to register the same scheme again + err := r.Register("existing", factory) + assert.Error(t, err) + assert.Contains(t, err.Error(), "resource is already registered with a given scheme:") +} + +func TestRegistry_Register_WithWhitespace(t *testing.T) { + r := newRegistry([]ResourceConfig{}) + + factory := func(u *url.URL) (Resource, error) { + return &TestResource{}, nil + } + + // Register with whitespace - should be trimmed + err := r.Register(" spaced ", factory) + assert.NoError(t, err) + + // Verify it was registered with trimmed name + rsc, err := r.Resolve("spaced://test") + assert.NoError(t, err) + assert.NotNil(t, rsc) +} + +func TestRegistry_Resolve_InvalidURL(t *testing.T) { + r := newRegistry([]ResourceConfig{}) + + // Test with invalid URL + rsc, err := r.Resolve("://invalid-url") + assert.Error(t, err) + assert.Nil(t, rsc) +} + +func TestRegistry_Resolve_UnknownScheme(t *testing.T) { + r := newRegistry([]ResourceConfig{}) + + // Test with unknown scheme + rsc, err := r.Resolve("unknown://test") + assert.Error(t, err) + assert.Nil(t, rsc) + assert.Contains(t, err.Error(), "resource with a given scheme is not found:") +} + +func TestRegistry_Resolve_FactoryError(t *testing.T) { + factory := func(u *url.URL) (Resource, error) { + return nil, errors.New("factory error") + } + + r := newRegistry([]ResourceConfig{ + { + Scheme: []string{"error"}, + Factory: factory, + }, + }) + + rsc, err := r.Resolve("error://test") + assert.Error(t, err) + assert.Nil(t, rsc) + assert.Contains(t, err.Error(), "factory error") +} + +func TestRegistry_List(t *testing.T) { + factory := func(u *url.URL) (Resource, error) { + return &TestResource{}, nil + } + + r := newRegistry([]ResourceConfig{ + { + Scheme: []string{"http", "https", "custom"}, + Factory: factory, + }, + }) + + schemes := r.List() + assert.Len(t, schemes, 3) + assert.Contains(t, schemes, "http") + assert.Contains(t, schemes, "https") + assert.Contains(t, schemes, "custom") +} + +func TestRegistry_List_Empty(t *testing.T) { + r := newRegistry([]ResourceConfig{}) + + schemes := r.List() + assert.Empty(t, schemes) +} diff --git a/waitfor_test.go b/waitfor_test.go new file mode 100644 index 0000000..d2501c9 --- /dev/null +++ b/waitfor_test.go @@ -0,0 +1,298 @@ +package waitfor + +import ( + "context" + "errors" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +// TestResourceSuccess is a mock resource that always succeeds +type TestResourceSuccess struct { + calls int +} + +func (t *TestResourceSuccess) Test(_ context.Context) error { + t.calls++ + return nil +} + +// TestResourceFailure is a mock resource that always fails +type TestResourceFailure struct { + calls int +} + +func (t *TestResourceFailure) Test(_ context.Context) error { + t.calls++ + return errors.New("resource not available") +} + +// MockResourceFactory creates test resources +func MockResourceFactory(u *url.URL) (Resource, error) { + if u.Host == "success" { + return &TestResourceSuccess{}, nil + } + if u.Host == "failure" { + return &TestResourceFailure{}, nil + } + return nil, errors.New("unknown host") +} + +func TestNew(t *testing.T) { + // Test with no configurators + runner := New() + assert.NotNil(t, runner) + assert.NotNil(t, runner.registry) + + // Test with configurators + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner = New(config) + assert.NotNil(t, runner) + assert.NotNil(t, runner.registry) +} + +func TestRunner_Resources(t *testing.T) { + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner := New(config) + + registry := runner.Resources() + assert.NotNil(t, registry) + assert.Equal(t, runner.registry, registry) +} + +func TestRunner_Test_Success(t *testing.T) { + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner := New(config) + + ctx := context.Background() + resources := []string{"test://success"} + + err := runner.Test(ctx, resources) + assert.NoError(t, err) +} + +func TestRunner_Test_SingleFailure(t *testing.T) { + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner := New(config) + + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + resources := []string{"test://failure"} + + err := runner.Test(ctx, resources, WithAttempts(1)) + assert.Error(t, err) + assert.Contains(t, err.Error(), ErrWait.Error()) +} + +func TestRunner_Test_MultipleResources(t *testing.T) { + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner := New(config) + + ctx := context.Background() + + // Test multiple successful resources + resources := []string{"test://success", "test://success"} + err := runner.Test(ctx, resources) + assert.NoError(t, err) + + // Test mix of success and failure with timeout + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + resources = []string{"test://success", "test://failure"} + err = runner.Test(ctx, resources, WithAttempts(1)) + assert.Error(t, err) + assert.Contains(t, err.Error(), ErrWait.Error()) +} + +func TestRunner_Test_WithOptions(t *testing.T) { + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner := New(config) + + ctx := context.Background() + resources := []string{"test://success"} + + // Test with custom options + err := runner.Test(ctx, resources, + WithAttempts(3), + WithInterval(1), + WithMaxInterval(10)) + assert.NoError(t, err) +} + +func TestRunner_Test_EmptyResources(t *testing.T) { + runner := New() + ctx := context.Background() + + // Test with empty resources slice + err := runner.Test(ctx, []string{}) + assert.NoError(t, err) +} + +func TestRunner_Run_Success(t *testing.T) { + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner := New(config) + + ctx := context.Background() + program := Program{ + Executable: "echo", + Args: []string{"hello"}, + Resources: []string{"test://success"}, + } + + output, err := runner.Run(ctx, program) + assert.NoError(t, err) + assert.Contains(t, string(output), "hello") +} + +func TestRunner_Run_ResourceFailure(t *testing.T) { + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner := New(config) + + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + program := Program{ + Executable: "echo", + Args: []string{"hello"}, + Resources: []string{"test://failure"}, + } + + output, err := runner.Run(ctx, program, WithAttempts(1)) + assert.Error(t, err) + assert.Nil(t, output) + assert.Contains(t, err.Error(), ErrWait.Error()) +} + +func TestRunner_Run_CommandFailure(t *testing.T) { + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner := New(config) + + ctx := context.Background() + program := Program{ + Executable: "nonexistent-command", + Args: []string{}, + Resources: []string{"test://success"}, + } + + _, err := runner.Run(ctx, program) + assert.Error(t, err) + // The important thing is that we get an error when the command fails +} + +func TestRunner_testInternal_ResolutionError(t *testing.T) { + runner := New() // No resources registered + + ctx := context.Background() + opts := Options{ + interval: time.Second, + maxInterval: time.Minute, + attempts: 1, + } + + err := runner.testInternal(ctx, "unknown://test", opts) + assert.Error(t, err) + assert.Contains(t, err.Error(), "resource with a given scheme is not found") +} + +func TestRunner_testInternal_ResourceTestError(t *testing.T) { + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner := New(config) + + ctx := context.Background() + opts := Options{ + interval: 1 * time.Millisecond, // Very short for testing + maxInterval: 2 * time.Millisecond, + attempts: 1, // Only one attempt to avoid long test time + } + + err := runner.testInternal(ctx, "test://failure", opts) + assert.Error(t, err) +} + +func TestRunner_testAllInternal(t *testing.T) { + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner := New(config) + + ctx := context.Background() + opts := Options{ + interval: time.Millisecond, + maxInterval: time.Millisecond * 10, + attempts: 1, + } + + // Test with multiple resources + resources := []string{"test://success", "test://success"} + output := runner.testAllInternal(ctx, resources, opts) + + errorCount := 0 + for err := range output { + if err != nil { + errorCount++ + } + } + + assert.Equal(t, 0, errorCount) +} + +func TestRunner_testAllInternal_WithErrors(t *testing.T) { + config := ResourceConfig{ + Scheme: []string{"test"}, + Factory: MockResourceFactory, + } + runner := New(config) + + ctx := context.Background() + opts := Options{ + interval: time.Millisecond, + maxInterval: time.Millisecond * 10, + attempts: 1, + } + + // Test with mix of success and failure + resources := []string{"test://success", "test://failure"} + output := runner.testAllInternal(ctx, resources, opts) + + errorCount := 0 + for err := range output { + if err != nil { + errorCount++ + } + } + + assert.Equal(t, 1, errorCount) +} \ No newline at end of file From d39d1c02a5aa04f3be97dac0503ff5500080b8b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 01:17:14 +0000 Subject: [PATCH 3/3] Fix linting errors by renaming unused parameters to underscore Co-authored-by: ziflex <1607148+ziflex@users.noreply.github.com> --- helpers_test.go | 4 ++-- registry_test.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/helpers_test.go b/helpers_test.go index 938bb7f..44893a4 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -11,7 +11,7 @@ func TestUse(t *testing.T) { // Create a mock module function mockModule := func() ([]string, ResourceFactory) { schemes := []string{"mock", "test"} - factory := func(u *url.URL) (Resource, error) { + factory := func(_ *url.URL) (Resource, error) { return &TestResource{}, nil } return schemes, factory @@ -34,7 +34,7 @@ func TestUse_WithEmptySchemes(t *testing.T) { // Create a module with empty schemes mockModule := func() ([]string, ResourceFactory) { schemes := []string{} - factory := func(u *url.URL) (Resource, error) { + factory := func(_ *url.URL) (Resource, error) { return &TestResource{}, nil } return schemes, factory diff --git a/registry_test.go b/registry_test.go index 3ba5bf3..fa0baf3 100644 --- a/registry_test.go +++ b/registry_test.go @@ -41,7 +41,7 @@ func TestRegistry_Register(t *testing.T) { func TestRegistry_Register_NewScheme(t *testing.T) { r := newRegistry([]ResourceConfig{}) - factory := func(u *url.URL) (Resource, error) { + factory := func(_ *url.URL) (Resource, error) { return &TestResource{}, nil } @@ -55,7 +55,7 @@ func TestRegistry_Register_NewScheme(t *testing.T) { } func TestRegistry_Register_DuplicateScheme(t *testing.T) { - factory := func(u *url.URL) (Resource, error) { + factory := func(_ *url.URL) (Resource, error) { return &TestResource{}, nil } @@ -75,7 +75,7 @@ func TestRegistry_Register_DuplicateScheme(t *testing.T) { func TestRegistry_Register_WithWhitespace(t *testing.T) { r := newRegistry([]ResourceConfig{}) - factory := func(u *url.URL) (Resource, error) { + factory := func(_ *url.URL) (Resource, error) { return &TestResource{}, nil } @@ -109,7 +109,7 @@ func TestRegistry_Resolve_UnknownScheme(t *testing.T) { } func TestRegistry_Resolve_FactoryError(t *testing.T) { - factory := func(u *url.URL) (Resource, error) { + factory := func(_ *url.URL) (Resource, error) { return nil, errors.New("factory error") } @@ -127,7 +127,7 @@ func TestRegistry_Resolve_FactoryError(t *testing.T) { } func TestRegistry_List(t *testing.T) { - factory := func(u *url.URL) (Resource, error) { + factory := func(_ *url.URL) (Resource, error) { return &TestResource{}, nil }