From 816beda7187757694ed425c0bd5b875b29f08da1 Mon Sep 17 00:00:00 2001 From: lutherwaves Date: Fri, 31 Oct 2025 21:02:22 +0200 Subject: [PATCH 1/2] cicd: add test results and lint annotations to PRs * Add test results annotations using JUnit format with dorny/test-reporter * Configure golangci-lint-action to show lint errors inline in code * Update CI workflow to run jobs independently in parallel --- .github/pull_request_template.md | 19 + .github/workflows/ci.yml | 40 +- errors/errors_test.go | 133 ++++ examples/main_test.go | 37 + health/health_test.go | 50 ++ leadership/leadership_test.go | 194 +++++ logger/logger_test.go | 95 +++ middlewares/auth_test.go | 1045 ++++++++++++++++++++++++++ middlewares/error_handler_test.go | 98 +++ middlewares/validator_test.go | 229 ++++++ mql/expr_test.go | 129 ++++ mql/parser_test.go | 220 ++++++ pubsub/publlisher_test.go | 36 + pubsub/sns_test.go | 49 ++ storage/cosmosdb_test.go | 664 ++++++++++++++++ storage/dynamodb_test.go | 539 +++++++++++++ storage/memory_test.go | 416 ++++++++++ storage/migration_test.go | 104 +++ storage/search/lucene/parser_test.go | 477 ++++++++++++ storage/sql_test.go | 488 ++++++++++++ storage/storage_test.go | 36 + types/types_test.go | 58 ++ utils/utils_test.go | 58 ++ 23 files changed, 5202 insertions(+), 12 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 errors/errors_test.go create mode 100644 examples/main_test.go create mode 100644 health/health_test.go create mode 100644 leadership/leadership_test.go create mode 100644 logger/logger_test.go create mode 100644 middlewares/auth_test.go create mode 100644 middlewares/error_handler_test.go create mode 100644 middlewares/validator_test.go create mode 100644 mql/expr_test.go create mode 100644 mql/parser_test.go create mode 100644 pubsub/publlisher_test.go create mode 100644 pubsub/sns_test.go create mode 100644 storage/cosmosdb_test.go create mode 100644 storage/dynamodb_test.go create mode 100644 storage/memory_test.go create mode 100644 storage/migration_test.go create mode 100644 storage/search/lucene/parser_test.go create mode 100644 storage/sql_test.go create mode 100644 storage/storage_test.go create mode 100644 types/types_test.go create mode 100644 utils/utils_test.go diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..4c1f095 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,19 @@ +## Description + + + +## Type of Change + + + +- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) +- [ ] ✨ New feature (non-breaking change which adds functionality) +- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] 📝 Documentation update +- [ ] ♻️ Refactoring (no functional changes) +- [ ] 🔧 Other (please describe): + +## Additional Notes + + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ac15f8..36c4771 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,11 @@ defaults: run: shell: bash +permissions: + contents: read + checks: write + pull-requests: write + jobs: conventional-commits: name: Conventional Commits Check @@ -37,29 +42,24 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - needs: conventional-commits timeout-minutes: 10 steps: - uses: actions/checkout@v4 - with: - fetch-depth: 1 - uses: actions/setup-go@v5 with: - go-version: '1.22.4' + go-version: '1.24.3' cache: true - - name: golangci-lint + - name: Lint uses: golangci/golangci-lint-action@v3 + continue-on-error: true with: version: latest + only-new-issues: true args: --timeout=5m - skip-cache: true - skip-pkg-cache: true - skip-build-cache: true test: name: Test runs-on: ubuntu-latest - needs: lint timeout-minutes: 10 steps: - uses: actions/checkout@v4 @@ -67,8 +67,24 @@ jobs: fetch-depth: 1 - uses: actions/setup-go@v5 with: - go-version: '1.22.4' + go-version: '1.24.3' cache: true - - name: Run tests - run: go test -v -cover ./... + - name: Install gotestsum + run: | + go install gotest.tools/gotestsum@latest + - name: Run tests with JUnit XML output + id: test + continue-on-error: true + run: | + gotestsum --junitfile test-results.xml --format standard-verbose ./... || true + - name: Generate test annotations + if: always() + uses: dorny/test-reporter@v1 + with: + name: Go Test Results + path: test-results.xml + reporter: java-junit + fail-on-error: false + list-suites: all + max-annotations: 50 diff --git a/errors/errors_test.go b/errors/errors_test.go new file mode 100644 index 0000000..d248166 --- /dev/null +++ b/errors/errors_test.go @@ -0,0 +1,133 @@ +package errors + +import "testing" + +func TestBadRequest_Error(t *testing.T) { + tests := []struct { + name string + e *BadRequest + want string + }{ + { + name: "returns message", + e: &BadRequest{Message: "invalid input"}, + want: "invalid input", + }, + { + name: "empty message returns empty string", + e: &BadRequest{Message: ""}, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.Error(); got != tt.want { + t.Errorf("BadRequest.Error() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNotFound_Error(t *testing.T) { + tests := []struct { + name string + e *NotFound + want string + }{ + { + name: "returns message", + e: &NotFound{Message: "resource not found"}, + want: "resource not found", + }, + { + name: "empty message returns empty string", + e: &NotFound{Message: ""}, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.Error(); got != tt.want { + t.Errorf("NotFound.Error() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestServiceUnavailable_Error(t *testing.T) { + tests := []struct { + name string + e *ServiceUnavailable + want string + }{ + { + name: "returns message", + e: &ServiceUnavailable{Message: "service unavailable"}, + want: "service unavailable", + }, + { + name: "empty message returns empty string", + e: &ServiceUnavailable{Message: ""}, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.Error(); got != tt.want { + t.Errorf("ServiceUnavailable.Error() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestForbidden_Error(t *testing.T) { + tests := []struct { + name string + e *Forbidden + want string + }{ + { + name: "returns message", + e: &Forbidden{Message: "access forbidden"}, + want: "access forbidden", + }, + { + name: "empty message returns empty string", + e: &Forbidden{Message: ""}, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.Error(); got != tt.want { + t.Errorf("Forbidden.Error() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnauthorized_Error(t *testing.T) { + tests := []struct { + name string + e *Unauthorized + want string + }{ + { + name: "returns message", + e: &Unauthorized{Message: "unauthorized"}, + want: "unauthorized", + }, + { + name: "empty message returns empty string", + e: &Unauthorized{Message: ""}, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.Error(); got != tt.want { + t.Errorf("Unauthorized.Error() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/examples/main_test.go b/examples/main_test.go new file mode 100644 index 0000000..aca026a --- /dev/null +++ b/examples/main_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "testing" + + "github.com/tink3rlabs/magic/storage" +) + +func Test_main(t *testing.T) { + tests := []struct { + name string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + main() + }) + } +} + +func Test_list(t *testing.T) { + type args struct { + s storage.StorageAdapter + } + tests := []struct { + name string + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + list(tt.args.s) + }) + } +} diff --git a/health/health_test.go b/health/health_test.go new file mode 100644 index 0000000..0fd78da --- /dev/null +++ b/health/health_test.go @@ -0,0 +1,50 @@ +package health + +import ( + "reflect" + "testing" + + "github.com/tink3rlabs/magic/storage" +) + +func TestNewHealthChecker(t *testing.T) { + type args struct { + storageAdapter storage.StorageAdapter + } + tests := []struct { + name string + args args + want *HealthChecker + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewHealthChecker(tt.args.storageAdapter); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewHealthChecker() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestHealthChecker_Check(t *testing.T) { + type args struct { + checkStorage bool + dependencies []string + } + tests := []struct { + name string + h *HealthChecker + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.h.Check(tt.args.checkStorage, tt.args.dependencies); (err != nil) != tt.wantErr { + t.Errorf("HealthChecker.Check() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/leadership/leadership_test.go b/leadership/leadership_test.go new file mode 100644 index 0000000..7b77652 --- /dev/null +++ b/leadership/leadership_test.go @@ -0,0 +1,194 @@ +package leadership + +import ( + "reflect" + "testing" +) + +func TestNewLeaderElection(t *testing.T) { + type args struct { + props LeaderElectionProps + } + tests := []struct { + name string + args args + want *LeaderElection + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewLeaderElection(tt.args.props); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewLeaderElection() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLeaderElection_createLeadershipTable(t *testing.T) { + tests := []struct { + name string + l *LeaderElection + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.l.createLeadershipTable(); (err != nil) != tt.wantErr { + t.Errorf("LeaderElection.createLeadershipTable() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestLeaderElection_updateMembershipTable(t *testing.T) { + tests := []struct { + name string + l *LeaderElection + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.l.updateMembershipTable(); (err != nil) != tt.wantErr { + t.Errorf("LeaderElection.updateMembershipTable() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestLeaderElection_removeMember(t *testing.T) { + type args struct { + memberId string + } + tests := []struct { + name string + l *LeaderElection + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.l.removeMember(tt.args.memberId); (err != nil) != tt.wantErr { + t.Errorf("LeaderElection.removeMember() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestLeaderElection_heartbeat(t *testing.T) { + tests := []struct { + name string + l *LeaderElection + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.l.heartbeat() + }) + } +} + +func TestLeaderElection_monitorLeader(t *testing.T) { + tests := []struct { + name string + l *LeaderElection + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.l.monitorLeader() + }) + } +} + +func TestLeaderElection_electLeader(t *testing.T) { + type args struct { + reElection bool + } + tests := []struct { + name string + l *LeaderElection + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.l.electLeader(tt.args.reElection); (err != nil) != tt.wantErr { + t.Errorf("LeaderElection.electLeader() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestLeaderElection_getLeader(t *testing.T) { + tests := []struct { + name string + l *LeaderElection + want Member + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.l.getLeader() + if (err != nil) != tt.wantErr { + t.Fatalf("LeaderElection.getLeader() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("LeaderElection.getLeader() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLeaderElection_Members(t *testing.T) { + tests := []struct { + name string + l *LeaderElection + want []Member + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.l.Members() + if (err != nil) != tt.wantErr { + t.Fatalf("LeaderElection.Members() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("LeaderElection.Members() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLeaderElection_Start(t *testing.T) { + tests := []struct { + name string + l *LeaderElection + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.l.Start() + }) + } +} diff --git a/logger/logger_test.go b/logger/logger_test.go new file mode 100644 index 0000000..1f700fe --- /dev/null +++ b/logger/logger_test.go @@ -0,0 +1,95 @@ +package logger + +import ( + "log/slog" + "testing" +) + +func TestMapLogLevel(t *testing.T) { + type args struct { + levelStr string + } + tests := []struct { + name string + args args + want slog.Level + }{ + { + name: "maps debug to LevelDebug", + args: args{levelStr: "debug"}, + want: slog.LevelDebug, + }, + { + name: "maps info to LevelInfo", + args: args{levelStr: "info"}, + want: slog.LevelInfo, + }, + { + name: "maps warn to LevelWarn", + args: args{levelStr: "warn"}, + want: slog.LevelWarn, + }, + { + name: "maps error to LevelError", + args: args{levelStr: "error"}, + want: slog.LevelError, + }, + { + name: "unknown level defaults to LevelInfo", + args: args{levelStr: "unknown"}, + want: slog.LevelInfo, + }, + { + name: "empty string defaults to LevelInfo", + args: args{levelStr: ""}, + want: slog.LevelInfo, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := MapLogLevel(tt.args.levelStr); got != tt.want { + t.Errorf("MapLogLevel() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestInit(t *testing.T) { + tests := []struct { + name string + config *Config + }{ + { + name: "initializes with JSON format", + config: &Config{ + Level: slog.LevelInfo, + JSON: true, + }, + }, + { + name: "initializes with text format", + config: &Config{ + Level: slog.LevelInfo, + JSON: false, + }, + }, + { + name: "initializes with debug level", + config: &Config{ + Level: slog.LevelDebug, + JSON: false, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + Init(tt.config) + defaultLogger := slog.Default() + if defaultLogger == nil { + t.Error("Init() should set default logger") + } + }) + } +} + +// Note: Fatal test is skipped as it calls os.Exit(1) which would terminate the test process diff --git a/middlewares/auth_test.go b/middlewares/auth_test.go new file mode 100644 index 0000000..bbffd61 --- /dev/null +++ b/middlewares/auth_test.go @@ -0,0 +1,1045 @@ +package middlewares + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "reflect" + "testing" +) + +func TestSetDefaultContextKeys(t *testing.T) { + originalKeys := DefaultContextKeys + defer func() { + DefaultContextKeys = originalKeys + }() + + type args struct { + keys ContextKeys + } + tests := []struct { + name string + args args + validate func(t *testing.T) + }{ + { + name: "set all keys", + args: args{ + keys: ContextKeys{ + Tenant: "custom-tenant-key", + UserId: "custom-user-key", + UserEmail: "custom-email-key", + Roles: "custom-roles-key", + Groups: "custom-groups-key", + }, + }, + validate: func(t *testing.T) { + if DefaultContextKeys.Tenant != "custom-tenant-key" { + t.Errorf("DefaultContextKeys.Tenant = %v, want custom-tenant-key", DefaultContextKeys.Tenant) + } + }, + }, + { + name: "set partial keys", + args: args{ + keys: ContextKeys{ + Tenant: "partial-tenant", + }, + }, + validate: func(t *testing.T) { + if DefaultContextKeys.Tenant != "partial-tenant" { + t.Errorf("DefaultContextKeys.Tenant = %v, want partial-tenant", DefaultContextKeys.Tenant) + } + }, + }, + { + name: "set nil keys should not override", + args: args{ + keys: ContextKeys{}, + }, + validate: func(t *testing.T) { + if DefaultContextKeys.Tenant == nil { + t.Error("DefaultContextKeys.Tenant should not be nil") + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + SetDefaultContextKeys(tt.args.keys) + if tt.validate != nil { + tt.validate(t) + } + }) + } +} + +func TestSetDefaultClaimsConfig(t *testing.T) { + originalConfig := DefaultClaimsConfig + defer func() { + DefaultClaimsConfig = originalConfig + }() + + type args struct { + cfg ClaimsConfig + } + tests := []struct { + name string + args args + validate func(t *testing.T) + }{ + { + name: "set all claim keys", + args: args{ + cfg: ClaimsConfig{ + TenantIdKey: "custom_org_id", + EmailKey: "custom_email", + RolesKey: "custom_roles", + GroupsKey: "custom_groups", + }, + }, + validate: func(t *testing.T) { + if DefaultClaimsConfig.TenantIdKey != "custom_org_id" { + t.Errorf("DefaultClaimsConfig.TenantIdKey = %v, want custom_org_id", DefaultClaimsConfig.TenantIdKey) + } + }, + }, + { + name: "set partial claim keys", + args: args{ + cfg: ClaimsConfig{ + RolesKey: "custom_roles", + }, + }, + validate: func(t *testing.T) { + if DefaultClaimsConfig.RolesKey != "custom_roles" { + t.Errorf("DefaultClaimsConfig.RolesKey = %v, want custom_roles", DefaultClaimsConfig.RolesKey) + } + }, + }, + { + name: "empty strings should not override", + args: args{ + cfg: ClaimsConfig{}, + }, + validate: func(t *testing.T) { + if DefaultClaimsConfig.TenantIdKey == "" { + t.Error("DefaultClaimsConfig.TenantIdKey should not be empty") + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + SetDefaultClaimsConfig(tt.args.cfg) + if tt.validate != nil { + tt.validate(t) + } + }) + } +} + +func Test_customClaims_UnmarshalJSON(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + c *customClaims + args args + wantErr bool + want map[string]any + }{ + { + name: "valid JSON with scope", + c: &customClaims{}, + args: args{ + data: []byte(`{"scope":"read write","org_id":"tenant123","email":"test@example.com"}`), + }, + wantErr: false, + want: map[string]any{ + "org_id": "tenant123", + "email": "test@example.com", + }, + }, + { + name: "valid JSON without scope", + c: &customClaims{}, + args: args{ + data: []byte(`{"org_id":"tenant123"}`), + }, + wantErr: false, + want: map[string]any{ + "org_id": "tenant123", + }, + }, + { + name: "invalid JSON", + c: &customClaims{}, + args: args{ + data: []byte(`{invalid json}`), + }, + wantErr: true, + }, + { + name: "empty JSON object", + c: &customClaims{}, + args: args{ + data: []byte(`{}`), + }, + wantErr: false, + want: map[string]any{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.c.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("customClaims.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && tt.want != nil { + for k, v := range tt.want { + if got, ok := tt.c.Claims[k]; !ok || got != v { + t.Errorf("customClaims.Claims[%q] = %v, want %v", k, got, v) + } + } + if tt.c.Scope != "" && len(tt.args.data) > 0 { + var raw map[string]any + json.Unmarshal(tt.args.data, &raw) + if scope, ok := raw["scope"]; ok { + if tt.c.Scope != scope { + t.Errorf("customClaims.Scope = %v, want %v", tt.c.Scope, scope) + } + } + } + } + }) + } +} + +func Test_customClaims_Validate(t *testing.T) { + type args struct { + ctx context.Context + } + tests := []struct { + name string + c *customClaims + args args + wantErr bool + }{ + { + name: "always returns nil", + c: &customClaims{}, + args: args{ctx: context.Background()}, + wantErr: false, + }, + { + name: "nil context", + c: &customClaims{}, + args: args{ctx: nil}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.c.Validate(tt.args.ctx); (err != nil) != tt.wantErr { + t.Errorf("customClaims.Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestEnsureValidToken(t *testing.T) { + tests := []struct { + name string + cfg EnsureValidTokenConfig + want func(http.Handler) http.Handler + // For functions, we test behavior instead of comparing + testBehavior func(t *testing.T, middleware func(http.Handler) http.Handler) + }{ + { + name: "disabled middleware should pass through", + cfg: EnsureValidTokenConfig{ + Enabled: false, + }, + testBehavior: func(t *testing.T, middleware func(http.Handler) http.Handler) { + called := false + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + }) + wrapped := middleware(handler) + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + wrapped.ServeHTTP(w, req) + if !called { + t.Error("handler should be called when middleware is disabled") + } + }, + }, + { + name: "enabled middleware should return function", + cfg: EnsureValidTokenConfig{ + Enabled: true, + IssuerURL: "https://example.com", + Audience: []string{"test"}, + }, + testBehavior: func(t *testing.T, middleware func(http.Handler) http.Handler) { + if middleware == nil { + t.Error("middleware should not be nil") + } + // Just verify it's a function - actual token validation would require real JWT setup + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + wrapped := middleware(handler) + if wrapped == nil { + t.Error("wrapped handler should not be nil") + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := EnsureValidToken(tt.cfg) + if got == nil { + t.Fatal("EnsureValidToken() returned nil") + } + if tt.testBehavior != nil { + tt.testBehavior(t, got) + } + }) + } +} + +func TestTenantRequestContext(t *testing.T) { + tests := []struct { + name string + next http.Handler + testBehavior func(t *testing.T, handler http.Handler) + }{ + { + name: "injects tenant from validated claims", + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tenant := GetTenantFromContext(r.Context()) + if tenant != "test-tenant" { + t.Errorf("GetTenantFromContext() = %v, want test-tenant", tenant) + } + }), + testBehavior: func(t *testing.T, handler http.Handler) { + claims := &validatedClaims{ + Subject: "user123", + CustomClaims: &customClaims{ + Claims: map[string]any{ + "org_id": "test-tenant", + }, + }, + } + ctx := context.WithValue(context.Background(), contextKeyValidatedClaims{}, claims) + req := httptest.NewRequest("GET", "/", nil).WithContext(ctx) + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + }, + }, + { + name: "injects empty tenant when no claims", + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tenant := GetTenantFromContext(r.Context()) + if tenant != "" { + t.Errorf("GetTenantFromContext() = %v, want empty string", tenant) + } + }), + testBehavior: func(t *testing.T, handler http.Handler) { + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := TenantRequestContext(tt.next) + if got == nil { + t.Fatal("TenantRequestContext() returned nil") + } + tt.testBehavior(t, got) + }) + } +} + +func TestUserRequestContext(t *testing.T) { + tests := []struct { + name string + next http.Handler + testBehavior func(t *testing.T, handler http.Handler) + }{ + { + name: "injects user info from validated claims", + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userID := GetUserIDFromContext(r.Context()) + email := GetEmailFromContext(r.Context()) + roles := GetRolesFromContext(r.Context()) + if userID != "user123" { + t.Errorf("GetUserIDFromContext() = %v, want user123", userID) + } + if email != "test@example.com" { + t.Errorf("GetEmailFromContext() = %v, want test@example.com", email) + } + if !reflect.DeepEqual(roles, []string{"admin"}) { + t.Errorf("GetRolesFromContext() = %v, want [admin]", roles) + } + }), + testBehavior: func(t *testing.T, handler http.Handler) { + claims := &validatedClaims{ + Subject: "user123", + CustomClaims: &customClaims{ + Claims: map[string]any{ + "email": "test@example.com", + "roles": []string{"admin"}, + }, + }, + } + ctx := context.WithValue(context.Background(), contextKeyValidatedClaims{}, claims) + req := httptest.NewRequest("GET", "/", nil).WithContext(ctx) + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + }, + }, + { + name: "injects empty values when no claims", + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if GetUserIDFromContext(r.Context()) != "" { + t.Error("GetUserIDFromContext() should return empty string") + } + }), + testBehavior: func(t *testing.T, handler http.Handler) { + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := UserRequestContext(tt.next) + if got == nil { + t.Fatal("UserRequestContext() returned nil") + } + tt.testBehavior(t, got) + }) + } +} + +func TestRequireRole(t *testing.T) { + tests := []struct { + name string + roleName string + testBehavior func(t *testing.T, middleware func(http.Handler) http.Handler) + }{ + { + name: "allows access when user has role", + roleName: "admin", + testBehavior: func(t *testing.T, middleware func(http.Handler) http.Handler) { + called := false + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + }) + wrapped := middleware(handler) + claims := &validatedClaims{ + Subject: "user123", + CustomClaims: &customClaims{ + Claims: map[string]any{ + "roles": []string{"admin", "user"}, + }, + }, + } + ctx := context.WithValue(context.Background(), contextKeyValidatedClaims{}, claims) + req := httptest.NewRequest("GET", "/", nil).WithContext(ctx) + w := httptest.NewRecorder() + wrapped.ServeHTTP(w, req) + if !called { + t.Error("handler should be called when user has required role") + } + if w.Code != http.StatusOK { + t.Errorf("status code = %v, want %v", w.Code, http.StatusOK) + } + }, + }, + { + name: "denies access when user lacks role", + roleName: "admin", + testBehavior: func(t *testing.T, middleware func(http.Handler) http.Handler) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Error("handler should not be called") + }) + wrapped := middleware(handler) + claims := &validatedClaims{ + Subject: "user123", + CustomClaims: &customClaims{ + Claims: map[string]any{ + "roles": []string{"user"}, + }, + }, + } + ctx := context.WithValue(context.Background(), contextKeyValidatedClaims{}, claims) + req := httptest.NewRequest("GET", "/", nil).WithContext(ctx) + w := httptest.NewRecorder() + wrapped.ServeHTTP(w, req) + if w.Code != http.StatusForbidden { + t.Errorf("status code = %v, want %v", w.Code, http.StatusForbidden) + } + }, + }, + { + name: "returns unauthorized when no claims", + roleName: "admin", + testBehavior: func(t *testing.T, middleware func(http.Handler) http.Handler) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Error("handler should not be called") + }) + wrapped := middleware(handler) + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + wrapped.ServeHTTP(w, req) + if w.Code != http.StatusUnauthorized { + t.Errorf("status code = %v, want %v", w.Code, http.StatusUnauthorized) + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := RequireRole(tt.roleName) + if got == nil { + t.Fatal("RequireRole() returned nil") + } + tt.testBehavior(t, got) + }) + } +} + +func TestGetUserIDFromContext(t *testing.T) { + type args struct { + ctx context.Context + } + tests := []struct { + name string + args args + want string + }{ + { + name: "returns user ID from context", + args: args{ + ctx: context.WithValue(context.Background(), DefaultContextKeys.UserId, "user123"), + }, + want: "user123", + }, + { + name: "returns empty string when not set", + args: args{ + ctx: context.Background(), + }, + want: "", + }, + { + name: "returns empty string when wrong type", + args: args{ + ctx: context.WithValue(context.Background(), DefaultContextKeys.UserId, 123), + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetUserIDFromContext(tt.args.ctx); got != tt.want { + t.Errorf("GetUserIDFromContext() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetEmailFromContext(t *testing.T) { + type args struct { + ctx context.Context + } + tests := []struct { + name string + args args + want string + }{ + { + name: "returns email from context", + args: args{ + ctx: context.WithValue(context.Background(), DefaultContextKeys.UserEmail, "test@example.com"), + }, + want: "test@example.com", + }, + { + name: "returns empty string when not set", + args: args{ + ctx: context.Background(), + }, + want: "", + }, + { + name: "returns empty string when wrong type", + args: args{ + ctx: context.WithValue(context.Background(), DefaultContextKeys.UserEmail, 123), + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetEmailFromContext(tt.args.ctx); got != tt.want { + t.Errorf("GetEmailFromContext() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetRolesFromContext(t *testing.T) { + type args struct { + ctx context.Context + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "returns roles from context", + args: args{ + ctx: context.WithValue(context.Background(), DefaultContextKeys.Roles, []string{"admin", "user"}), + }, + want: []string{"admin", "user"}, + }, + { + name: "returns nil when not set", + args: args{ + ctx: context.Background(), + }, + want: nil, + }, + { + name: "returns nil when wrong type", + args: args{ + ctx: context.WithValue(context.Background(), DefaultContextKeys.Roles, "not-a-slice"), + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetRolesFromContext(tt.args.ctx); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetRolesFromContext() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetGroupsFromContext(t *testing.T) { + type args struct { + ctx context.Context + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "returns groups from context", + args: args{ + ctx: context.WithValue(context.Background(), DefaultContextKeys.Groups, []string{"group1", "group2"}), + }, + want: []string{"group1", "group2"}, + }, + { + name: "returns nil when not set", + args: args{ + ctx: context.Background(), + }, + want: nil, + }, + { + name: "returns nil when wrong type", + args: args{ + ctx: context.WithValue(context.Background(), DefaultContextKeys.Groups, "not-a-slice"), + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetGroupsFromContext(tt.args.ctx); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetGroupsFromContext() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetTenantFromContext(t *testing.T) { + type args struct { + ctx context.Context + } + tests := []struct { + name string + args args + want string + }{ + { + name: "returns tenant from context", + args: args{ + ctx: context.WithValue(context.Background(), DefaultContextKeys.Tenant, "tenant123"), + }, + want: "tenant123", + }, + { + name: "returns empty string when not set", + args: args{ + ctx: context.Background(), + }, + want: "", + }, + { + name: "returns empty string when wrong type", + args: args{ + ctx: context.WithValue(context.Background(), DefaultContextKeys.Tenant, 123), + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetTenantFromContext(tt.args.ctx); got != tt.want { + t.Errorf("GetTenantFromContext() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getValidatedClaims(t *testing.T) { + type args struct { + ctx context.Context + } + tests := []struct { + name string + args args + want *validatedClaims + }{ + { + name: "returns validated claims from context", + args: args{ + ctx: context.WithValue(context.Background(), contextKeyValidatedClaims{}, &validatedClaims{ + Subject: "user123", + CustomClaims: &customClaims{}, + }), + }, + want: &validatedClaims{ + Subject: "user123", + CustomClaims: &customClaims{}, + }, + }, + { + name: "returns nil when not set", + args: args{ + ctx: context.Background(), + }, + want: nil, + }, + { + name: "returns nil when wrong type", + args: args{ + ctx: context.WithValue(context.Background(), contextKeyValidatedClaims{}, "not-claims"), + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getValidatedClaims(tt.args.ctx) + if tt.want == nil { + if got != nil { + t.Errorf("getValidatedClaims() = %v, want nil", got) + } + } else { + if got == nil || got.Subject != tt.want.Subject { + t.Errorf("getValidatedClaims() = %v, want %v", got, tt.want) + } + } + }) + } +} + +func Test_getRoles(t *testing.T) { + type args struct { + c *customClaims + cfg ClaimsConfig + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "returns roles from []string", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "roles": []string{"admin", "user"}, + }, + }, + cfg: DefaultClaimsConfig, + }, + want: []string{"admin", "user"}, + }, + { + name: "returns roles from []any", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "roles": []any{"admin", "user"}, + }, + }, + cfg: DefaultClaimsConfig, + }, + want: []string{"admin", "user"}, + }, + { + name: "returns nil when not found", + args: args{ + c: &customClaims{ + Claims: map[string]any{}, + }, + cfg: DefaultClaimsConfig, + }, + want: nil, + }, + { + name: "returns nil for wrong type", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "roles": "not-a-slice", + }, + }, + cfg: DefaultClaimsConfig, + }, + want: nil, + }, + { + name: "uses custom roles key", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "custom_roles": []string{"admin"}, + }, + }, + cfg: ClaimsConfig{RolesKey: "custom_roles"}, + }, + want: []string{"admin"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getRoles(tt.args.c, tt.args.cfg); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getRoles() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getGroups(t *testing.T) { + type args struct { + c *customClaims + cfg ClaimsConfig + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "returns groups from []string", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "groups": []string{"group1", "group2"}, + }, + }, + cfg: DefaultClaimsConfig, + }, + want: []string{"group1", "group2"}, + }, + { + name: "returns groups from []any", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "groups": []any{"group1", "group2"}, + }, + }, + cfg: DefaultClaimsConfig, + }, + want: []string{"group1", "group2"}, + }, + { + name: "returns nil when not found", + args: args{ + c: &customClaims{ + Claims: map[string]any{}, + }, + cfg: DefaultClaimsConfig, + }, + want: nil, + }, + { + name: "uses custom groups key", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "custom_groups": []string{"group1"}, + }, + }, + cfg: ClaimsConfig{GroupsKey: "custom_groups"}, + }, + want: []string{"group1"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getGroups(tt.args.c, tt.args.cfg); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getGroups() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getTenant(t *testing.T) { + type args struct { + c *customClaims + cfg ClaimsConfig + } + tests := []struct { + name string + args args + want string + }{ + { + name: "returns tenant from claims", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "org_id": "tenant123", + }, + }, + cfg: DefaultClaimsConfig, + }, + want: "tenant123", + }, + { + name: "returns empty string when not found", + args: args{ + c: &customClaims{ + Claims: map[string]any{}, + }, + cfg: DefaultClaimsConfig, + }, + want: "", + }, + { + name: "returns empty string for wrong type", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "org_id": 123, + }, + }, + cfg: DefaultClaimsConfig, + }, + want: "", + }, + { + name: "uses custom tenant key", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "custom_org": "tenant123", + }, + }, + cfg: ClaimsConfig{TenantIdKey: "custom_org"}, + }, + want: "tenant123", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getTenant(tt.args.c, tt.args.cfg); got != tt.want { + t.Errorf("getTenant() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getEmail(t *testing.T) { + type args struct { + c *customClaims + cfg ClaimsConfig + } + tests := []struct { + name string + args args + want string + }{ + { + name: "returns email from claims", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "email": "test@example.com", + }, + }, + cfg: DefaultClaimsConfig, + }, + want: "test@example.com", + }, + { + name: "returns empty string when not found", + args: args{ + c: &customClaims{ + Claims: map[string]any{}, + }, + cfg: DefaultClaimsConfig, + }, + want: "", + }, + { + name: "returns empty string for wrong type", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "email": 123, + }, + }, + cfg: DefaultClaimsConfig, + }, + want: "", + }, + { + name: "uses custom email key", + args: args{ + c: &customClaims{ + Claims: map[string]any{ + "custom_email": "test@example.com", + }, + }, + cfg: ClaimsConfig{EmailKey: "custom_email"}, + }, + want: "test@example.com", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getEmail(tt.args.c, tt.args.cfg); got != tt.want { + t.Errorf("getEmail() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/middlewares/error_handler_test.go b/middlewares/error_handler_test.go new file mode 100644 index 0000000..720ddda --- /dev/null +++ b/middlewares/error_handler_test.go @@ -0,0 +1,98 @@ +package middlewares + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + serviceErrors "github.com/tink3rlabs/magic/errors" + "github.com/tink3rlabs/magic/storage" +) + +func TestErrorHandler_Wrap(t *testing.T) { + tests := []struct { + name string + e *ErrorHandler + handler func(w http.ResponseWriter, r *http.Request) error + wantStatusCode int + wantBody string + }{ + { + name: "handles NotFound error", + e: &ErrorHandler{}, + handler: func(w http.ResponseWriter, r *http.Request) error { + return &serviceErrors.NotFound{Message: "resource not found"} + }, + wantStatusCode: http.StatusNotFound, + }, + { + name: "handles storage.ErrNotFound", + e: &ErrorHandler{}, + handler: func(w http.ResponseWriter, r *http.Request) error { + return storage.ErrNotFound + }, + wantStatusCode: http.StatusNotFound, + }, + { + name: "handles BadRequest error", + e: &ErrorHandler{}, + handler: func(w http.ResponseWriter, r *http.Request) error { + return &serviceErrors.BadRequest{Message: "invalid request"} + }, + wantStatusCode: http.StatusBadRequest, + }, + { + name: "handles ServiceUnavailable error", + e: &ErrorHandler{}, + handler: func(w http.ResponseWriter, r *http.Request) error { + return &serviceErrors.ServiceUnavailable{Message: "service unavailable"} + }, + wantStatusCode: http.StatusServiceUnavailable, + }, + { + name: "handles Forbidden error", + e: &ErrorHandler{}, + handler: func(w http.ResponseWriter, r *http.Request) error { + return &serviceErrors.Forbidden{Message: "access forbidden"} + }, + wantStatusCode: http.StatusForbidden, + }, + { + name: "handles Unauthorized error", + e: &ErrorHandler{}, + handler: func(w http.ResponseWriter, r *http.Request) error { + return &serviceErrors.Unauthorized{Message: "unauthorized"} + }, + wantStatusCode: http.StatusUnauthorized, + }, + { + name: "handles generic error", + e: &ErrorHandler{}, + handler: func(w http.ResponseWriter, r *http.Request) error { + return errors.New("generic error") + }, + wantStatusCode: http.StatusInternalServerError, + }, + { + name: "no error passes through", + e: &ErrorHandler{}, + handler: func(w http.ResponseWriter, r *http.Request) error { + w.WriteHeader(http.StatusOK) + return nil + }, + wantStatusCode: http.StatusOK, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.e.Wrap(tt.handler) + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + got.ServeHTTP(w, req) + if w.Code != tt.wantStatusCode { + t.Errorf("ErrorHandler.Wrap() status code = %v, want %v", w.Code, tt.wantStatusCode) + } + }) + } +} diff --git a/middlewares/validator_test.go b/middlewares/validator_test.go new file mode 100644 index 0000000..2cfe5a1 --- /dev/null +++ b/middlewares/validator_test.go @@ -0,0 +1,229 @@ +package middlewares + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/go-chi/chi/v5" +) + +func TestJSONSchemaValidator(t *testing.T) { + type args struct { + schema string + data interface{} + } + tests := []struct { + name string + args args + want ValidationResult + wantErr bool + }{ + { + name: "valid data passes validation", + args: args{ + schema: `{ + "type": "object", + "properties": { + "name": {"type": "string"} + }, + "required": ["name"] + }`, + data: map[string]interface{}{ + "name": "test", + }, + }, + want: ValidationResult{ + Result: true, + Error: []string{}, + }, + wantErr: false, + }, + { + name: "invalid data fails validation", + args: args{ + schema: `{ + "type": "object", + "properties": { + "name": {"type": "string"} + }, + "required": ["name"] + }`, + data: map[string]interface{}{}, + }, + want: ValidationResult{ + Result: false, + Error: []string{}, // Will be populated by validator + }, + wantErr: false, + }, + { + name: "invalid schema returns error", + args: args{ + schema: `{invalid json}`, + data: map[string]interface{}{}, + }, + wantErr: true, + }, + { + name: "valid number passes validation", + args: args{ + schema: `{ + "type": "object", + "properties": { + "age": {"type": "number"} + } + }`, + data: map[string]interface{}{ + "age": 25, + }, + }, + want: ValidationResult{ + Result: true, + Error: []string{}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := JSONSchemaValidator(tt.args.schema, tt.args.data) + if (err != nil) != tt.wantErr { + t.Fatalf("JSONSchemaValidator() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got.Result != tt.want.Result { + t.Errorf("JSONSchemaValidator() Result = %v, want %v", got.Result, tt.want.Result) + } + if !got.Result && len(got.Error) == 0 { + t.Error("JSONSchemaValidator() should return errors when Result is false") + } + }) + } +} + +func TestValidator_ValidateRequest(t *testing.T) { + tests := []struct { + name string + f *Validator + schemas map[string]string + next http.HandlerFunc + requestSetup func(*http.Request) + wantStatusCode int + }{ + { + name: "valid body passes through", + f: &Validator{}, + schemas: map[string]string{ + "body": `{ + "type": "object", + "properties": { + "name": {"type": "string"} + }, + "required": ["name"] + }`, + }, + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }), + requestSetup: func(r *http.Request) { + r.Body = io.NopCloser(strings.NewReader(`{"name": "test"}`)) + }, + wantStatusCode: http.StatusOK, + }, + { + name: "invalid body returns bad request", + f: &Validator{}, + schemas: map[string]string{ + "body": `{ + "type": "object", + "properties": { + "name": {"type": "string"} + }, + "required": ["name"] + }`, + }, + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Error("next handler should not be called") + }), + requestSetup: func(r *http.Request) { + r.Body = io.NopCloser(strings.NewReader(`{}`)) + }, + wantStatusCode: http.StatusBadRequest, + }, + { + name: "valid query passes through", + f: &Validator{}, + schemas: map[string]string{ + "query": `{ + "type": "object", + "properties": { + "limit": {"type": "string"} + } + }`, + }, + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }), + requestSetup: func(r *http.Request) { + // Query params would be set via URL + }, + wantStatusCode: http.StatusOK, + }, + { + name: "valid params passes through", + f: &Validator{}, + schemas: map[string]string{ + "params": `{ + "type": "object", + "properties": { + "id": {"type": "string"} + } + }`, + }, + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }), + requestSetup: func(r *http.Request) { + rctx := chi.NewRouteContext() + rctx.URLParams.Add("id", "123") + ctx := context.WithValue(r.Context(), chi.RouteCtxKey, rctx) + *r = *r.WithContext(ctx) + }, + wantStatusCode: http.StatusOK, + }, + { + name: "invalid schema returns internal server error", + f: &Validator{}, + schemas: map[string]string{ + "body": `{invalid json}`, + }, + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Error("next handler should not be called") + }), + requestSetup: func(r *http.Request) { + r.Body = io.NopCloser(strings.NewReader(`{"name": "test"}`)) + }, + wantStatusCode: http.StatusInternalServerError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.f.ValidateRequest(tt.schemas, tt.next) + req := httptest.NewRequest("GET", "/", nil) + if tt.requestSetup != nil { + tt.requestSetup(req) + } + w := httptest.NewRecorder() + got.ServeHTTP(w, req) + if w.Code != tt.wantStatusCode { + t.Errorf("Validator.ValidateRequest() status code = %v, want %v", w.Code, tt.wantStatusCode) + } + }) + } +} diff --git a/mql/expr_test.go b/mql/expr_test.go new file mode 100644 index 0000000..051b441 --- /dev/null +++ b/mql/expr_test.go @@ -0,0 +1,129 @@ +package mql + +import "testing" + +func TestBinaryExpr_Eval(t *testing.T) { + type args struct { + input map[string]interface{} + } + tests := []struct { + name string + e *BinaryExpr + args args + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.Eval(tt.args.input); got != tt.want { + t.Errorf("BinaryExpr.Eval() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNotExpr_Eval(t *testing.T) { + type args struct { + input map[string]interface{} + } + tests := []struct { + name string + e *NotExpr + args args + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.Eval(tt.args.input); got != tt.want { + t.Errorf("NotExpr.Eval() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGroupExpr_Eval(t *testing.T) { + type args struct { + input map[string]interface{} + } + tests := []struct { + name string + e *GroupExpr + args args + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.Eval(tt.args.input); got != tt.want { + t.Errorf("GroupExpr.Eval() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTermExpr_Eval(t *testing.T) { + type args struct { + input map[string]interface{} + } + tests := []struct { + name string + e *TermExpr + args args + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.Eval(tt.args.input); got != tt.want { + t.Errorf("TermExpr.Eval() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_wildcardMatch(t *testing.T) { + type args struct { + value string + pattern string + } + tests := []struct { + name string + args args + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := wildcardMatch(tt.args.value, tt.args.pattern); got != tt.want { + t.Errorf("wildcardMatch() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_listContains(t *testing.T) { + type args struct { + list []string + val string + } + tests := []struct { + name string + args args + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := listContains(tt.args.list, tt.args.val); got != tt.want { + t.Errorf("listContains() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/mql/parser_test.go b/mql/parser_test.go new file mode 100644 index 0000000..612e665 --- /dev/null +++ b/mql/parser_test.go @@ -0,0 +1,220 @@ +package mql + +import ( + "reflect" + "testing" +) + +func TestNewParser(t *testing.T) { + type args struct { + input string + } + tests := []struct { + name string + args args + want *Parser + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewParser(tt.args.input); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewParser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_next(t *testing.T) { + tests := []struct { + name string + p *Parser + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.p.next() + }) + } +} + +func TestParser_Parse(t *testing.T) { + tests := []struct { + name string + p *Parser + want Expr + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.Parse() + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.Parse() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.Parse() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_parseOr(t *testing.T) { + tests := []struct { + name string + p *Parser + want Expr + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.parseOr() + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.parseOr() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.parseOr() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_parseAnd(t *testing.T) { + tests := []struct { + name string + p *Parser + want Expr + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.parseAnd() + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.parseAnd() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.parseAnd() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_parseUnary(t *testing.T) { + tests := []struct { + name string + p *Parser + want Expr + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.parseUnary() + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.parseUnary() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.parseUnary() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_parsePrimary(t *testing.T) { + tests := []struct { + name string + p *Parser + want Expr + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.parsePrimary() + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.parsePrimary() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.parsePrimary() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_parseTerm(t *testing.T) { + tests := []struct { + name string + p *Parser + want Expr + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.parseTerm() + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.parseTerm() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.parseTerm() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_parseList(t *testing.T) { + type args struct { + key string + op string + } + tests := []struct { + name string + p *Parser + args args + want Expr + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.parseList(tt.args.key, tt.args.op) + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.parseList() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.parseList() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pubsub/publlisher_test.go b/pubsub/publlisher_test.go new file mode 100644 index 0000000..223df83 --- /dev/null +++ b/pubsub/publlisher_test.go @@ -0,0 +1,36 @@ +package pubsub + +import ( + "reflect" + "testing" +) + +func TestPublisherFactory_GetInstance(t *testing.T) { + type args struct { + publisherType PublisherType + config any + } + tests := []struct { + name string + s PublisherFactory + args args + want Publisher + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.GetInstance(tt.args.publisherType, tt.args.config) + if (err != nil) != tt.wantErr { + t.Fatalf("PublisherFactory.GetInstance() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("PublisherFactory.GetInstance() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pubsub/sns_test.go b/pubsub/sns_test.go new file mode 100644 index 0000000..8ef4053 --- /dev/null +++ b/pubsub/sns_test.go @@ -0,0 +1,49 @@ +package pubsub + +import ( + "reflect" + "testing" +) + +func TestGetSNSPublisher(t *testing.T) { + type args struct { + config map[string]string + } + tests := []struct { + name string + args args + want *SNSPublisher + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetSNSPublisher(tt.args.config); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetSNSPublisher() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSNSPublisher_Publish(t *testing.T) { + type args struct { + topic string + message string + params map[string]any + } + tests := []struct { + name string + s *SNSPublisher + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Publish(tt.args.topic, tt.args.message, tt.args.params); (err != nil) != tt.wantErr { + t.Errorf("SNSPublisher.Publish() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/storage/cosmosdb_test.go b/storage/cosmosdb_test.go new file mode 100644 index 0000000..42f2c7a --- /dev/null +++ b/storage/cosmosdb_test.go @@ -0,0 +1,664 @@ +package storage + +import ( + "reflect" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" +) + +func TestGetCosmosDBAdapterInstance(t *testing.T) { + type args struct { + config map[string]string + } + tests := []struct { + name string + args args + want *CosmosDBAdapter + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetCosmosDBAdapterInstance(tt.args.config); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetCosmosDBAdapterInstance() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_OpenConnection(t *testing.T) { + tests := []struct { + name string + s *CosmosDBAdapter + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.OpenConnection() + }) + } +} + +func TestCosmosDBAdapter_Execute(t *testing.T) { + type args struct { + statement string + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Execute(tt.args.statement); (err != nil) != tt.wantErr { + t.Errorf("CosmosDBAdapter.Execute() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCosmosDBAdapter_Ping(t *testing.T) { + tests := []struct { + name string + s *CosmosDBAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Ping(); (err != nil) != tt.wantErr { + t.Errorf("CosmosDBAdapter.Ping() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCosmosDBAdapter_GetType(t *testing.T) { + tests := []struct { + name string + s *CosmosDBAdapter + want StorageAdapterType + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.GetType(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CosmosDBAdapter.GetType() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_GetProvider(t *testing.T) { + tests := []struct { + name string + s *CosmosDBAdapter + want StorageProviders + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.GetProvider(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CosmosDBAdapter.GetProvider() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_GetSchemaName(t *testing.T) { + tests := []struct { + name string + s *CosmosDBAdapter + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.GetSchemaName(); got != tt.want { + t.Errorf("CosmosDBAdapter.GetSchemaName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_CreateSchema(t *testing.T) { + tests := []struct { + name string + s *CosmosDBAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.CreateSchema(); (err != nil) != tt.wantErr { + t.Errorf("CosmosDBAdapter.CreateSchema() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCosmosDBAdapter_CreateMigrationTable(t *testing.T) { + tests := []struct { + name string + s *CosmosDBAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.CreateMigrationTable(); (err != nil) != tt.wantErr { + t.Errorf("CosmosDBAdapter.CreateMigrationTable() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCosmosDBAdapter_UpdateMigrationTable(t *testing.T) { + type args struct { + id int + name string + desc string + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.UpdateMigrationTable(tt.args.id, tt.args.name, tt.args.desc); (err != nil) != tt.wantErr { + t.Errorf("CosmosDBAdapter.UpdateMigrationTable() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCosmosDBAdapter_GetLatestMigration(t *testing.T) { + tests := []struct { + name string + s *CosmosDBAdapter + want int + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.GetLatestMigration() + if (err != nil) != tt.wantErr { + t.Fatalf("CosmosDBAdapter.GetLatestMigration() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("CosmosDBAdapter.GetLatestMigration() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_Create(t *testing.T) { + type args struct { + item any + params []map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Create(tt.args.item, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("CosmosDBAdapter.Create() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCosmosDBAdapter_Get(t *testing.T) { + type args struct { + dest any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Get(tt.args.dest, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("CosmosDBAdapter.Get() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCosmosDBAdapter_Update(t *testing.T) { + type args struct { + item any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Update(tt.args.item, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("CosmosDBAdapter.Update() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCosmosDBAdapter_Delete(t *testing.T) { + type args struct { + item any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Delete(tt.args.item, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("CosmosDBAdapter.Delete() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCosmosDBAdapter_List(t *testing.T) { + type args struct { + dest any + sortKey string + filter map[string]any + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.List(tt.args.dest, tt.args.sortKey, tt.args.filter, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("CosmosDBAdapter.List() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("CosmosDBAdapter.List() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_Search(t *testing.T) { + type args struct { + dest any + sortKey string + query string + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Search(tt.args.dest, tt.args.sortKey, tt.args.query, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("CosmosDBAdapter.Search() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("CosmosDBAdapter.Search() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_Count(t *testing.T) { + type args struct { + dest any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want int64 + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Count(tt.args.dest, tt.args.filter, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("CosmosDBAdapter.Count() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("CosmosDBAdapter.Count() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_Query(t *testing.T) { + type args struct { + dest any + statement string + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Query(tt.args.dest, tt.args.statement, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("CosmosDBAdapter.Query() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("CosmosDBAdapter.Query() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_executePaginatedQuery(t *testing.T) { + type args struct { + dest any + sortKey string + sortDirection string + limit int + cursor string + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.executePaginatedQuery(tt.args.dest, tt.args.sortKey, tt.args.sortDirection, tt.args.limit, tt.args.cursor, tt.args.filter, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("CosmosDBAdapter.executePaginatedQuery() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("CosmosDBAdapter.executePaginatedQuery() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_getContainerName(t *testing.T) { + type args struct { + obj any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.getContainerName(tt.args.obj); got != tt.want { + t.Errorf("CosmosDBAdapter.getContainerName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_itemToMap(t *testing.T) { + type args struct { + item any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want map[string]interface{} + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.itemToMap(tt.args.item); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CosmosDBAdapter.itemToMap() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_buildPartitionKey(t *testing.T) { + type args struct { + paramMap map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.buildPartitionKey(tt.args.paramMap) + if (err != nil) != tt.wantErr { + t.Fatalf("CosmosDBAdapter.buildPartitionKey() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("CosmosDBAdapter.buildPartitionKey() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_getPartitionKeyFieldName(t *testing.T) { + type args struct { + paramMap map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.getPartitionKeyFieldName(tt.args.paramMap); got != tt.want { + t.Errorf("CosmosDBAdapter.getPartitionKeyFieldName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_executeQuery(t *testing.T) { + type args struct { + containerClient *azcosmos.ContainerClient + query string + paramMap map[string]any + queryOptions *azcosmos.QueryOptions + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want azcosmos.QueryItemsResponse + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.executeQuery(tt.args.containerClient, tt.args.query, tt.args.paramMap, tt.args.queryOptions) + if (err != nil) != tt.wantErr { + t.Fatalf("CosmosDBAdapter.executeQuery() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("CosmosDBAdapter.executeQuery() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_extractParams(t *testing.T) { + type args struct { + params []map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want map[string]any + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.extractParams(tt.args.params...); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CosmosDBAdapter.extractParams() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_extractSortDirection(t *testing.T) { + type args struct { + paramMap map[string]any + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.extractSortDirection(tt.args.paramMap); got != tt.want { + t.Errorf("CosmosDBAdapter.extractSortDirection() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCosmosDBAdapter_buildFilter(t *testing.T) { + type args struct { + filter map[string]any + paramIndex *int + } + tests := []struct { + name string + s *CosmosDBAdapter + args args + want string + want1 []azcosmos.QueryParameter + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := tt.s.buildFilter(tt.args.filter, tt.args.paramIndex) + if got != tt.want { + t.Errorf("CosmosDBAdapter.buildFilter() got = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(got1, tt.want1) { + t.Errorf("CosmosDBAdapter.buildFilter() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/storage/dynamodb_test.go b/storage/dynamodb_test.go new file mode 100644 index 0000000..39c360c --- /dev/null +++ b/storage/dynamodb_test.go @@ -0,0 +1,539 @@ +package storage + +import ( + "reflect" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" +) + +func TestGetDynamoDBAdapterInstance(t *testing.T) { + type args struct { + config map[string]string + } + tests := []struct { + name string + args args + want *DynamoDBAdapter + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetDynamoDBAdapterInstance(tt.args.config); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetDynamoDBAdapterInstance() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_OpenConnection(t *testing.T) { + tests := []struct { + name string + s *DynamoDBAdapter + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.OpenConnection() + }) + } +} + +func TestDynamoDBAdapter_Execute(t *testing.T) { + type args struct { + statement string + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Execute(tt.args.statement); (err != nil) != tt.wantErr { + t.Errorf("DynamoDBAdapter.Execute() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDynamoDBAdapter_Ping(t *testing.T) { + tests := []struct { + name string + s *DynamoDBAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Ping(); (err != nil) != tt.wantErr { + t.Errorf("DynamoDBAdapter.Ping() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDynamoDBAdapter_GetType(t *testing.T) { + tests := []struct { + name string + s *DynamoDBAdapter + want StorageAdapterType + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.GetType(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("DynamoDBAdapter.GetType() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_GetProvider(t *testing.T) { + tests := []struct { + name string + s *DynamoDBAdapter + want StorageProviders + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.GetProvider(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("DynamoDBAdapter.GetProvider() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_GetSchemaName(t *testing.T) { + tests := []struct { + name string + s *DynamoDBAdapter + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.GetSchemaName(); got != tt.want { + t.Errorf("DynamoDBAdapter.GetSchemaName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_CreateSchema(t *testing.T) { + tests := []struct { + name string + s *DynamoDBAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.CreateSchema(); (err != nil) != tt.wantErr { + t.Errorf("DynamoDBAdapter.CreateSchema() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDynamoDBAdapter_CreateMigrationTable(t *testing.T) { + tests := []struct { + name string + s *DynamoDBAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.CreateMigrationTable(); (err != nil) != tt.wantErr { + t.Errorf("DynamoDBAdapter.CreateMigrationTable() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDynamoDBAdapter_UpdateMigrationTable(t *testing.T) { + type args struct { + id int + name string + desc string + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.UpdateMigrationTable(tt.args.id, tt.args.name, tt.args.desc); (err != nil) != tt.wantErr { + t.Errorf("DynamoDBAdapter.UpdateMigrationTable() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDynamoDBAdapter_GetLatestMigration(t *testing.T) { + tests := []struct { + name string + s *DynamoDBAdapter + want int + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.GetLatestMigration() + if (err != nil) != tt.wantErr { + t.Fatalf("DynamoDBAdapter.GetLatestMigration() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("DynamoDBAdapter.GetLatestMigration() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_Create(t *testing.T) { + type args struct { + item any + params []map[string]any + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Create(tt.args.item, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("DynamoDBAdapter.Create() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDynamoDBAdapter_Get(t *testing.T) { + type args struct { + dest any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Get(tt.args.dest, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("DynamoDBAdapter.Get() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDynamoDBAdapter_Update(t *testing.T) { + type args struct { + item any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Update(tt.args.item, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("DynamoDBAdapter.Update() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDynamoDBAdapter_Delete(t *testing.T) { + type args struct { + item any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Delete(tt.args.item, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("DynamoDBAdapter.Delete() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDynamoDBAdapter_executePaginatedQuery(t *testing.T) { + type args struct { + dest any + limit int + cursor string + builder dynamoQueryBuilder + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.executePaginatedQuery(tt.args.dest, tt.args.limit, tt.args.cursor, tt.args.builder) + if (err != nil) != tt.wantErr { + t.Fatalf("DynamoDBAdapter.executePaginatedQuery() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("DynamoDBAdapter.executePaginatedQuery() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_List(t *testing.T) { + type args struct { + dest any + sortKey string + filter map[string]any + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.List(tt.args.dest, tt.args.sortKey, tt.args.filter, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("DynamoDBAdapter.List() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("DynamoDBAdapter.List() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_Search(t *testing.T) { + type args struct { + dest any + sortKey string + query string + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Search(tt.args.dest, tt.args.sortKey, tt.args.query, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("DynamoDBAdapter.Search() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("DynamoDBAdapter.Search() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_Count(t *testing.T) { + type args struct { + dest any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + want int64 + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Count(tt.args.dest, tt.args.filter, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("DynamoDBAdapter.Count() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("DynamoDBAdapter.Count() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_Query(t *testing.T) { + type args struct { + dest any + statement string + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Query(tt.args.dest, tt.args.statement, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("DynamoDBAdapter.Query() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("DynamoDBAdapter.Query() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_getTableName(t *testing.T) { + type args struct { + obj any + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.getTableName(tt.args.obj); got != tt.want { + t.Errorf("DynamoDBAdapter.getTableName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_buildFilter(t *testing.T) { + type args struct { + filter map[string]any + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.buildFilter(tt.args.filter); got != tt.want { + t.Errorf("DynamoDBAdapter.buildFilter() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDynamoDBAdapter_buildParams(t *testing.T) { + type args struct { + filter map[string]any + } + tests := []struct { + name string + s *DynamoDBAdapter + args args + want []types.AttributeValue + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.buildParams(tt.args.filter) + if (err != nil) != tt.wantErr { + t.Fatalf("DynamoDBAdapter.buildParams() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("DynamoDBAdapter.buildParams() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/storage/memory_test.go b/storage/memory_test.go new file mode 100644 index 0000000..84dd51d --- /dev/null +++ b/storage/memory_test.go @@ -0,0 +1,416 @@ +package storage + +import ( + "reflect" + "testing" +) + +func TestGetMemoryAdapterInstance(t *testing.T) { + tests := []struct { + name string + want *MemoryAdapter + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetMemoryAdapterInstance(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetMemoryAdapterInstance() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMemoryAdapter_Execute(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + m *MemoryAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.m.Execute(tt.args.s); (err != nil) != tt.wantErr { + t.Errorf("MemoryAdapter.Execute() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMemoryAdapter_Ping(t *testing.T) { + tests := []struct { + name string + m *MemoryAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.m.Ping(); (err != nil) != tt.wantErr { + t.Errorf("MemoryAdapter.Ping() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMemoryAdapter_GetType(t *testing.T) { + tests := []struct { + name string + m *MemoryAdapter + want StorageAdapterType + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.GetType(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MemoryAdapter.GetType() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMemoryAdapter_GetProvider(t *testing.T) { + tests := []struct { + name string + m *MemoryAdapter + want StorageProviders + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.GetProvider(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MemoryAdapter.GetProvider() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMemoryAdapter_GetSchemaName(t *testing.T) { + tests := []struct { + name string + m *MemoryAdapter + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.GetSchemaName(); got != tt.want { + t.Errorf("MemoryAdapter.GetSchemaName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMemoryAdapter_CreateSchema(t *testing.T) { + tests := []struct { + name string + m *MemoryAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.m.CreateSchema(); (err != nil) != tt.wantErr { + t.Errorf("MemoryAdapter.CreateSchema() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMemoryAdapter_CreateMigrationTable(t *testing.T) { + tests := []struct { + name string + m *MemoryAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.m.CreateMigrationTable(); (err != nil) != tt.wantErr { + t.Errorf("MemoryAdapter.CreateMigrationTable() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMemoryAdapter_UpdateMigrationTable(t *testing.T) { + type args struct { + id int + name string + desc string + } + tests := []struct { + name string + m *MemoryAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.m.UpdateMigrationTable(tt.args.id, tt.args.name, tt.args.desc); (err != nil) != tt.wantErr { + t.Errorf("MemoryAdapter.UpdateMigrationTable() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMemoryAdapter_GetLatestMigration(t *testing.T) { + tests := []struct { + name string + m *MemoryAdapter + want int + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.m.GetLatestMigration() + if (err != nil) != tt.wantErr { + t.Fatalf("MemoryAdapter.GetLatestMigration() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("MemoryAdapter.GetLatestMigration() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMemoryAdapter_Create(t *testing.T) { + type args struct { + item any + params []map[string]any + } + tests := []struct { + name string + m *MemoryAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.m.Create(tt.args.item, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("MemoryAdapter.Create() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMemoryAdapter_Get(t *testing.T) { + type args struct { + dest any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + m *MemoryAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.m.Get(tt.args.dest, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("MemoryAdapter.Get() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMemoryAdapter_Update(t *testing.T) { + type args struct { + item any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + m *MemoryAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.m.Update(tt.args.item, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("MemoryAdapter.Update() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMemoryAdapter_Delete(t *testing.T) { + type args struct { + item any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + m *MemoryAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.m.Delete(tt.args.item, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("MemoryAdapter.Delete() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMemoryAdapter_List(t *testing.T) { + type args struct { + dest any + sortKey string + filter map[string]any + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + m *MemoryAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.m.List(tt.args.dest, tt.args.sortKey, tt.args.filter, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("MemoryAdapter.List() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("MemoryAdapter.List() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMemoryAdapter_Search(t *testing.T) { + type args struct { + dest any + sortKey string + query string + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + m *MemoryAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.m.Search(tt.args.dest, tt.args.sortKey, tt.args.query, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("MemoryAdapter.Search() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("MemoryAdapter.Search() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMemoryAdapter_Count(t *testing.T) { + type args struct { + dest any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + m *MemoryAdapter + args args + want int64 + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.m.Count(tt.args.dest, tt.args.filter, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("MemoryAdapter.Count() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("MemoryAdapter.Count() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMemoryAdapter_Query(t *testing.T) { + type args struct { + dest any + statement string + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + m *MemoryAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.m.Query(tt.args.dest, tt.args.statement, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("MemoryAdapter.Query() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("MemoryAdapter.Query() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/storage/migration_test.go b/storage/migration_test.go new file mode 100644 index 0000000..0eba0f3 --- /dev/null +++ b/storage/migration_test.go @@ -0,0 +1,104 @@ +package storage + +import ( + "reflect" + "testing" +) + +func TestNewDatabaseMigration(t *testing.T) { + type args struct { + storageAdapter StorageAdapter + } + tests := []struct { + name string + args args + want *DatabaseMigration + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewDatabaseMigration(tt.args.storageAdapter); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewDatabaseMigration() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDatabaseMigration_getMigrationFiles(t *testing.T) { + tests := []struct { + name string + m *DatabaseMigration + want map[string]MigrationFile + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.m.getMigrationFiles() + if (err != nil) != tt.wantErr { + t.Fatalf("DatabaseMigration.getMigrationFiles() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("DatabaseMigration.getMigrationFiles() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDatabaseMigration_rollbackMigration(t *testing.T) { + type args struct { + migration MigrationFile + } + tests := []struct { + name string + m *DatabaseMigration + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.m.rollbackMigration(tt.args.migration); (err != nil) != tt.wantErr { + t.Errorf("DatabaseMigration.rollbackMigration() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDatabaseMigration_runMigrations(t *testing.T) { + type args struct { + migrations map[string]MigrationFile + } + tests := []struct { + name string + m *DatabaseMigration + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.m.runMigrations(tt.args.migrations) + }) + } +} + +func TestDatabaseMigration_Migrate(t *testing.T) { + tests := []struct { + name string + m *DatabaseMigration + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.m.Migrate() + }) + } +} diff --git a/storage/search/lucene/parser_test.go b/storage/search/lucene/parser_test.go new file mode 100644 index 0000000..bd69a11 --- /dev/null +++ b/storage/search/lucene/parser_test.go @@ -0,0 +1,477 @@ +package lucene + +import ( + "reflect" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" +) + +func TestNewParserFromType(t *testing.T) { + type args struct { + model any + } + tests := []struct { + name string + args args + want *Parser + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewParserFromType(tt.args.model) + if (err != nil) != tt.wantErr { + t.Fatalf("NewParserFromType() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewParserFromType() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewParser(t *testing.T) { + type args struct { + defaultFields []FieldInfo + } + tests := []struct { + name string + args args + want *Parser + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewParser(tt.args.defaultFields); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewParser() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getStructFields(t *testing.T) { + type args struct { + model any + } + tests := []struct { + name string + args args + want []FieldInfo + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getStructFields(tt.args.model) + if (err != nil) != tt.wantErr { + t.Fatalf("getStructFields() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("getStructFields() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_ParseToMap(t *testing.T) { + type args struct { + query string + } + tests := []struct { + name string + p *Parser + args args + want map[string]any + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.ParseToMap(tt.args.query) + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.ParseToMap() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.ParseToMap() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_ParseToSQL(t *testing.T) { + type args struct { + query string + } + tests := []struct { + name string + p *Parser + args args + want string + want1 []any + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := tt.p.ParseToSQL(tt.args.query) + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.ParseToSQL() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("Parser.ParseToSQL() got = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(got1, tt.want1) { + t.Errorf("Parser.ParseToSQL() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestParser_parse(t *testing.T) { + type args struct { + query string + } + tests := []struct { + name string + p *Parser + args args + want *Node + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.parse(tt.args.query) + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.parse() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.parse() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_splitByOperator(t *testing.T) { + type args struct { + input string + op string + } + tests := []struct { + name string + args args + want []string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := splitByOperator(tt.args.input, tt.args.op); !reflect.DeepEqual(got, tt.want) { + t.Errorf("splitByOperator() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_createImplicitNode(t *testing.T) { + type args struct { + term string + } + tests := []struct { + name string + p *Parser + args args + want *Node + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.createImplicitNode(tt.args.term) + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.createImplicitNode() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.createImplicitNode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_createWildcardNode(t *testing.T) { + type args struct { + field string + value string + } + tests := []struct { + name string + p *Parser + args args + want *Node + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.createWildcardNode(tt.args.field, tt.args.value) + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.createWildcardNode() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.createWildcardNode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_formatFieldName(t *testing.T) { + type args struct { + fieldName string + } + tests := []struct { + name string + p *Parser + args args + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.p.formatFieldName(tt.args.fieldName); got != tt.want { + t.Errorf("Parser.formatFieldName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_createTermNode(t *testing.T) { + type args struct { + field string + value string + } + tests := []struct { + name string + p *Parser + args args + want *Node + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.createTermNode(tt.args.field, tt.args.value) + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.createTermNode() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.createTermNode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_createLogicalNode(t *testing.T) { + type args struct { + op LogicalOperator + parts []string + } + tests := []struct { + name string + p *Parser + args args + want *Node + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.createLogicalNode(tt.args.op, tt.args.parts) + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.createLogicalNode() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.createLogicalNode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_nodeToMap(t *testing.T) { + type args struct { + node *Node + } + tests := []struct { + name string + p *Parser + args args + want map[string]any + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.p.nodeToMap(tt.args.node); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Parser.nodeToMap() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParser_nodeToSQL(t *testing.T) { + type args struct { + node *Node + } + tests := []struct { + name string + p *Parser + args args + want string + want1 []any + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := tt.p.nodeToSQL(tt.args.node) + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.nodeToSQL() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("Parser.nodeToSQL() got = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(got1, tt.want1) { + t.Errorf("Parser.nodeToSQL() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestParser_ParseToDynamoDBPartiQL(t *testing.T) { + type args struct { + query string + } + tests := []struct { + name string + p *Parser + args args + want string + want1 []types.AttributeValue + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := tt.p.ParseToDynamoDBPartiQL(tt.args.query) + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.ParseToDynamoDBPartiQL() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("Parser.ParseToDynamoDBPartiQL() got = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(got1, tt.want1) { + t.Errorf("Parser.ParseToDynamoDBPartiQL() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestParser_nodeToDynamoDBPartiQL(t *testing.T) { + type args struct { + node *Node + } + tests := []struct { + name string + p *Parser + args args + want string + want1 []types.AttributeValue + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := tt.p.nodeToDynamoDBPartiQL(tt.args.node) + if (err != nil) != tt.wantErr { + t.Fatalf("Parser.nodeToDynamoDBPartiQL() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("Parser.nodeToDynamoDBPartiQL() got = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(got1, tt.want1) { + t.Errorf("Parser.nodeToDynamoDBPartiQL() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func Test_wildcardToPattern(t *testing.T) { + type args struct { + value string + matchType MatchType + } + tests := []struct { + name string + args args + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := wildcardToPattern(tt.args.value, tt.args.matchType); got != tt.want { + t.Errorf("wildcardToPattern() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/storage/sql_test.go b/storage/sql_test.go new file mode 100644 index 0000000..71a4d7a --- /dev/null +++ b/storage/sql_test.go @@ -0,0 +1,488 @@ +package storage + +import ( + "reflect" + "testing" +) + +func TestGetSQLAdapterInstance(t *testing.T) { + type args struct { + config map[string]string + } + tests := []struct { + name string + args args + want *SQLAdapter + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetSQLAdapterInstance(tt.args.config); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetSQLAdapterInstance() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSQLAdapter_OpenConnection(t *testing.T) { + tests := []struct { + name string + s *SQLAdapter + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.OpenConnection() + }) + } +} + +func TestSQLAdapter_Execute(t *testing.T) { + type args struct { + statement string + } + tests := []struct { + name string + s *SQLAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Execute(tt.args.statement); (err != nil) != tt.wantErr { + t.Errorf("SQLAdapter.Execute() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestSQLAdapter_Ping(t *testing.T) { + tests := []struct { + name string + s *SQLAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Ping(); (err != nil) != tt.wantErr { + t.Errorf("SQLAdapter.Ping() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestSQLAdapter_GetType(t *testing.T) { + tests := []struct { + name string + s *SQLAdapter + want StorageAdapterType + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.GetType(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("SQLAdapter.GetType() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSQLAdapter_GetProvider(t *testing.T) { + tests := []struct { + name string + s *SQLAdapter + want StorageProviders + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.GetProvider(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("SQLAdapter.GetProvider() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSQLAdapter_GetSchemaName(t *testing.T) { + tests := []struct { + name string + s *SQLAdapter + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.GetSchemaName(); got != tt.want { + t.Errorf("SQLAdapter.GetSchemaName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSQLAdapter_CreateSchema(t *testing.T) { + tests := []struct { + name string + s *SQLAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.CreateSchema(); (err != nil) != tt.wantErr { + t.Errorf("SQLAdapter.CreateSchema() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestSQLAdapter_CreateMigrationTable(t *testing.T) { + tests := []struct { + name string + s *SQLAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.CreateMigrationTable(); (err != nil) != tt.wantErr { + t.Errorf("SQLAdapter.CreateMigrationTable() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestSQLAdapter_UpdateMigrationTable(t *testing.T) { + type args struct { + id int + name string + desc string + } + tests := []struct { + name string + s *SQLAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.UpdateMigrationTable(tt.args.id, tt.args.name, tt.args.desc); (err != nil) != tt.wantErr { + t.Errorf("SQLAdapter.UpdateMigrationTable() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestSQLAdapter_GetLatestMigration(t *testing.T) { + tests := []struct { + name string + s *SQLAdapter + want int + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.GetLatestMigration() + if (err != nil) != tt.wantErr { + t.Fatalf("SQLAdapter.GetLatestMigration() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("SQLAdapter.GetLatestMigration() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSQLAdapter_Create(t *testing.T) { + type args struct { + item any + params []map[string]any + } + tests := []struct { + name string + s *SQLAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Create(tt.args.item, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("SQLAdapter.Create() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestSQLAdapter_Get(t *testing.T) { + type args struct { + dest any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *SQLAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Get(tt.args.dest, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("SQLAdapter.Get() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestSQLAdapter_Update(t *testing.T) { + type args struct { + item any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *SQLAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Update(tt.args.item, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("SQLAdapter.Update() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestSQLAdapter_Delete(t *testing.T) { + type args struct { + item any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *SQLAdapter + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Delete(tt.args.item, tt.args.filter, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("SQLAdapter.Delete() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestSQLAdapter_executePaginatedQuery(t *testing.T) { + type args struct { + dest any + sortKey string + limit int + cursor string + builder queryBuilder + } + tests := []struct { + name string + s *SQLAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.executePaginatedQuery(tt.args.dest, tt.args.sortKey, tt.args.limit, tt.args.cursor, tt.args.builder) + if (err != nil) != tt.wantErr { + t.Fatalf("SQLAdapter.executePaginatedQuery() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("SQLAdapter.executePaginatedQuery() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSQLAdapter_List(t *testing.T) { + type args struct { + dest any + sortKey string + filter map[string]any + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + s *SQLAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.List(tt.args.dest, tt.args.sortKey, tt.args.filter, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("SQLAdapter.List() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("SQLAdapter.List() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSQLAdapter_Search(t *testing.T) { + type args struct { + dest any + sortKey string + query string + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + s *SQLAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Search(tt.args.dest, tt.args.sortKey, tt.args.query, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("SQLAdapter.Search() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("SQLAdapter.Search() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSQLAdapter_Count(t *testing.T) { + type args struct { + dest any + filter map[string]any + params []map[string]any + } + tests := []struct { + name string + s *SQLAdapter + args args + want int64 + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Count(tt.args.dest, tt.args.filter, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("SQLAdapter.Count() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("SQLAdapter.Count() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSQLAdapter_Query(t *testing.T) { + type args struct { + dest any + statement string + limit int + cursor string + params []map[string]any + } + tests := []struct { + name string + s *SQLAdapter + args args + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.Query(tt.args.dest, tt.args.statement, tt.args.limit, tt.args.cursor, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Fatalf("SQLAdapter.Query() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if got != tt.want { + t.Errorf("SQLAdapter.Query() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSQLAdapter_buildQuery(t *testing.T) { + type args struct { + filter map[string]any + } + tests := []struct { + name string + s *SQLAdapter + args args + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.buildQuery(tt.args.filter); got != tt.want { + t.Errorf("SQLAdapter.buildQuery() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/storage/storage_test.go b/storage/storage_test.go new file mode 100644 index 0000000..22f7f82 --- /dev/null +++ b/storage/storage_test.go @@ -0,0 +1,36 @@ +package storage + +import ( + "reflect" + "testing" +) + +func TestStorageAdapterFactory_GetInstance(t *testing.T) { + type args struct { + adapterType StorageAdapterType + config any + } + tests := []struct { + name string + s StorageAdapterFactory + args args + want StorageAdapter + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.GetInstance(tt.args.adapterType, tt.args.config) + if (err != nil) != tt.wantErr { + t.Fatalf("StorageAdapterFactory.GetInstance() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("StorageAdapterFactory.GetInstance() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/types/types_test.go b/types/types_test.go new file mode 100644 index 0000000..f471334 --- /dev/null +++ b/types/types_test.go @@ -0,0 +1,58 @@ +package types + +import ( + "reflect" + "testing" +) + +func TestGetOpenAPIDefinitions(t *testing.T) { + tests := []struct { + name string + want []byte + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetOpenAPIDefinitions() + if (err != nil) != tt.wantErr { + t.Fatalf("GetOpenAPIDefinitions() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetOpenAPIDefinitions() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMergeOpenAPIDefinitions(t *testing.T) { + type args struct { + inputDefinition []byte + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := MergeOpenAPIDefinitions(tt.args.inputDefinition) + if (err != nil) != tt.wantErr { + t.Fatalf("MergeOpenAPIDefinitions() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MergeOpenAPIDefinitions() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/utils/utils_test.go b/utils/utils_test.go new file mode 100644 index 0000000..454620e --- /dev/null +++ b/utils/utils_test.go @@ -0,0 +1,58 @@ +package utils + +import ( + "testing" +) + +func TestNewId(t *testing.T) { + tests := []struct { + name string + wantErr bool + validate func(t *testing.T, got *string) + }{ + { + name: "generates valid ID", + wantErr: false, + validate: func(t *testing.T, got *string) { + if got == nil { + t.Error("NewId() returned nil") + return + } + if *got == "" { + t.Error("NewId() returned empty string") + } + if len(*got) == 0 { + t.Error("NewId() returned ID with zero length") + } + }, + }, + { + name: "generates unique IDs", + wantErr: false, + validate: func(t *testing.T, got *string) { + id2, err := NewId() + if err != nil { + t.Errorf("NewId() error = %v", err) + return + } + if got != nil && id2 != nil && *got == *id2 { + t.Error("NewId() generated duplicate IDs") + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewId() + if (err != nil) != tt.wantErr { + t.Fatalf("NewId() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + if tt.validate != nil { + tt.validate(t, got) + } + }) + } +} From 83603b7a0cf07c3c301c8480df81a9d101b7e3b0 Mon Sep 17 00:00:00 2001 From: lutherwaves Date: Tue, 16 Dec 2025 00:11:49 +0200 Subject: [PATCH 2/2] feat: test adding as comments, update readme --- .github/workflows/README.md | 93 ++++++++++++++++++++++++++++++++++ .github/workflows/SETUP.md | 44 +++++++++++++++++ .github/workflows/ci.yml | 10 +++- .github/workflows/test.yml | 99 +++++++++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/SETUP.md create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..4bfdb5b --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,93 @@ +# GitHub Actions Workflows + +This directory contains reusable workflows for Go projects. + +## Features + +- ✅ **Test Results as Artifacts**: Downloadable from workflow run summary +- ✅ **PR Comments**: Test summary posted directly in PR comments +- ✅ **Check Annotations**: Failed tests appear as annotations in the PR checks +- ✅ **Path Filtering**: Only runs when Go files change +- ✅ **Skip CI Support**: Respects `[skip-ci]` flag +- ✅ **Private Module Support**: Configured for private Go modules + +## Test Results in Pull Requests + +### Artifacts vs Comments + +**GitHub Actions Limitations:** +- Unlike GitLab, GitHub Actions **cannot** display artifacts directly inline in PRs +- Artifacts are stored separately and must be downloaded from the workflow run summary +- The standard approach for showing test results in GitHub PRs is through **PR comments** and **check annotations** + +**Our Solution:** +We use a **dual approach**: +1. **Artifacts**: Test results are uploaded as artifacts for downloadability and archival +2. **PR Comments**: Test results are posted as comments on PRs using `dorny/test-reporter` action + +### How to Use in Other Repositories + +1. **Copy the workflow** to your repository: + ```bash + cp .github/workflows/test.yml /path/to/repo/.github/workflows/test.yml + ``` + +2. **Customize as needed**: + - Adjust `GOPRIVATE` environment variable if needed + - Modify test command if you need coverage or specific test flags + - Add/remove path filters in the `on:` section + - Adjust timeout values + +3. **Ensure permissions** are set correctly: + ```yaml + permissions: + contents: read + checks: write + pull-requests: write + ``` + +4. **For private modules**, ensure `GH_ACCESS_TOKEN` secret is configured in your repository settings. + +### Features + +- ✅ **Test Results as Artifacts**: Downloadable from workflow run summary +- ✅ **PR Comments**: Test summary posted directly in PR comments +- ✅ **Check Annotations**: Failed tests appear as annotations in the PR checks +- ✅ **Generic & Reusable**: Works across all Go repositories +- ✅ **Path Filtering**: Only runs when relevant files change +- ✅ **Skip CI Support**: Respects `[skip-ci]` flag + +### Example Output + +When tests run, you'll see: +- **In PR Comments**: A formatted test summary with pass/fail counts +- **In PR Checks**: Individual test failures as annotations +- **In Artifacts**: Downloadable `test-results.xml` file + +### Customization Examples + +**Add coverage:** +```yaml +- name: Run tests with coverage + run: | + gotestsum --junitfile test-results.xml --format standard-verbose -coverprofile=coverage.out ./... +``` + +**Test specific packages:** +```yaml +- name: Run tests + run: | + gotestsum --junitfile test-results.xml --format standard-verbose ./pkg/... ./cmd/... +``` + +**Add custom paths:** +```yaml +on: + push: + paths: + - '**/*.go' + - 'go.mod' + - 'go.sum' + - '.github/**' # Add this to trigger on workflow changes +``` + diff --git a/.github/workflows/SETUP.md b/.github/workflows/SETUP.md new file mode 100644 index 0000000..a0e426f --- /dev/null +++ b/.github/workflows/SETUP.md @@ -0,0 +1,44 @@ +# Quick Setup Guide + +## Copy Test Workflow to Other Repositories + +Run this command from the `magic` repository root: + +```bash +# For base repository +cp .github/workflows/test.yml ../source/base/.github/workflows/test.yml + +# For ledge repository +cp .github/workflows/test.yml ../source/ledge/.github/workflows/test.yml + +# For flow repository +cp .github/workflows/test.yml ../source/flow/.github/workflows/test.yml + +# For wire repository +cp .github/workflows/test.yml ../source/wire/.github/workflows/test.yml + +# For imagine repository +cp .github/workflows/test.yml ../source/imagine/.github/workflows/test.yml + +# For matchblox repository +cp .github/workflows/test.yml ../source/matchblox/.github/workflows/test.yml + +# For profile repository +cp .github/workflows/test.yml ../source/profile/.github/workflows/test.yml +``` + +## After Copying + +1. **Review the workflow file** - Ensure paths and configurations match your repository +2. **Check permissions** - Verify the workflow has `pull-requests: write` permission +3. **Test it** - Create a test PR to verify test results appear correctly +4. **Remove old test steps** - If updating existing workflows, remove duplicate test steps + +## What You Get + +- ✅ Test results posted as PR comments +- ✅ Test results uploaded as downloadable artifacts +- ✅ Failed tests shown as check annotations +- ✅ Only runs when Go files change +- ✅ Respects `[skip-ci]` flag + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36c4771..af322b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,14 @@ jobs: continue-on-error: true run: | gotestsum --junitfile test-results.xml --format standard-verbose ./... || true - - name: Generate test annotations + - name: Upload test results as artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ github.run_number }} + path: test-results.xml + retention-days: 30 + - name: Generate test annotations and PR comment if: always() uses: dorny/test-reporter@v1 with: @@ -87,4 +94,5 @@ jobs: fail-on-error: false list-suites: all max-annotations: 50 + only-summary: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..90a8b7e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,99 @@ +name: Test + +on: + push: + branches: [main] + paths: + - '**/*.go' + - 'go.mod' + - 'go.sum' + pull_request: + branches: [main] + paths: + - '**/*.go' + - 'go.mod' + - 'go.sum' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +# Configure GOPRIVATE if your project uses private Go modules +env: + GOPRIVATE: github.com/blox-eng/* + +permissions: + contents: read + checks: write + pull-requests: write + +jobs: + changes: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + go: ${{ steps.changes-go.outputs.exists }} + steps: + - uses: yumemi-inc/path-filter@v2 + id: changes-go + with: + patterns: '**/*.go' + + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: changes + # Skip CI if [skip-ci] is in commit message or PR title + if: ${{ !contains(github.event.head_commit.message, '[skip-ci]') && !contains(github.event.pull_request.title, '[skip-ci]') && needs.changes.outputs.go == 'true' }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Configure git for private modules + if: env.GOPRIVATE != '' + run: | + git config --global url."https://x-access-token:${{ secrets.GH_ACCESS_TOKEN }}@github.com/".insteadOf "https://github.com/" + + - uses: actions/setup-go@v5 + with: + go-version: '1.24.3' + cache: true + + - name: Install gotestsum + run: | + go install gotest.tools/gotestsum@latest + + - name: Run tests with JUnit XML output + id: test + continue-on-error: true + # Customize test command here if needed (e.g., add coverage, specific packages, etc.) + run: | + gotestsum --junitfile test-results.xml --format standard-verbose ./... || true + + - name: Upload test results as artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ github.run_number }} + path: test-results.xml + retention-days: 30 + + - name: Generate test annotations and PR comment + if: always() + uses: dorny/test-reporter@v1 + with: + name: Go Test Results + path: test-results.xml + reporter: java-junit + fail-on-error: false + list-suites: all + max-annotations: 50 + only-summary: false +