From da3b83cb83affbae779476e198e16e4cb2562bea Mon Sep 17 00:00:00 2001 From: Tanvir Ahmed Date: Tue, 7 Jul 2026 01:00:06 +0000 Subject: [PATCH] refactor: Support Postgres only - Removed support for SQLite and MySQL - Removed SQLite watermill provider - Only Postgres migrations are supported now - Integration tests spin up local postgres container which replaced sqlite in-memory - Removed various Golang package dependencies such as SQLite, MySQL and more - Removed dependency on CGO --- .agents/skills/tdd-unit-testing/SKILL.md | 4 +- .../examples/repository_test.go | 51 +- .devcontainer/Dockerfile | 2 - Dockerfile | 4 +- Makefile | 13 +- README.md | 2 +- auth.go | 6 +- bootstrap.go | 3 +- cmd/migrate/main.go | 8 +- config/options.go | 11 - events/constants.go | 3 +- go.mod | 56 +- go.sum | 160 ------ internal/bootstrap/database.go | 62 +-- internal/events/watermill_providers.go | 60 --- internal/events/watermill_providers_test.go | 477 ++++++------------ internal/migrationmanager/manager.go | 22 +- internal/plugins/plugin_registry.go | 5 +- .../bun_session_repository_test.go | 14 +- internal/testdb/db_helpers.go | 169 +++++++ internal/tests/base_integration_fixture.go | 5 +- internal/tests/db_helpers.go | 210 +------- migrations/core.go | 383 +++----------- migrations/migrator_test.go | 20 +- migrations/provider_variants.go | 24 - models/config.go | 6 - models/plugin.go | 2 +- plugins/access-control/migrations.go | 4 +- .../access-control/migrationset/migrations.go | 160 +----- plugins/access-control/plugin.go | 4 +- .../permissions_repository_test.go | 69 ++- .../role_permission_repository_test.go | 96 ++-- .../repositories/roles_repository_test.go | 105 ++-- .../user_permissions_repository_test.go | 68 ++- .../user_roles_repository_test.go | 84 +-- plugins/access-control/tests/test_helpers.go | 23 +- plugins/admin/migrations.go | 233 ++------- plugins/admin/plugin.go | 4 +- .../impersonation_repository_test.go | 2 +- .../repositories/session_state_repository.go | 72 +-- .../session_state_repository_test.go | 2 +- .../user_state_repository_test.go | 27 +- plugins/api-key/migrations.go | 4 +- plugins/api-key/migrationset/migrations.go | 103 +--- plugins/api-key/plugin.go | 4 +- .../bun_api_key_repository_test.go | 16 +- plugins/jwt/migrations.go | 4 +- plugins/jwt/migrationset/migrations.go | 99 +--- plugins/jwt/plugin.go | 4 +- .../jwt/repositories/jwks_repository_test.go | 22 +- .../refresh_token_repository_test.go | 57 ++- plugins/jwt/services/cache_service_test.go | 20 +- plugins/jwt/services/key_service_test.go | 40 +- plugins/organizations/migrations.go | 4 +- .../organizations/migrationset/migrations.go | 223 +------- plugins/organizations/plugin.go | 4 +- ...organization_invitation_repository_test.go | 247 +++++---- ...bun_organization_member_repository_test.go | 247 +++++---- .../bun_organization_repository_test.go | 103 ++-- ...rganization_team_member_repository_test.go | 328 +++++++----- .../bun_organization_team_repository_test.go | 154 ++++-- plugins/organizations/tests/repositories.go | 1 - plugins/organizations/tests/test_helpers.go | 21 +- plugins/rate-limit/migrations.go | 4 +- plugins/rate-limit/migrationset/migrations.go | 93 +--- plugins/rate-limit/plugin.go | 4 +- .../bun_rate_limit_repository_test.go | 12 +- .../services/database_provider_test.go | 2 +- .../database_secondary_storage_test.go | 42 +- plugins/secondary-storage/migrations.go | 98 +--- plugins/secondary-storage/plugin.go | 4 +- plugins/totp/migrations.go | 154 ++---- plugins/totp/plugin.go | 4 +- plugins/totp/plugin_test.go | 42 +- .../totp/repository/totp_repository_test.go | 238 +++++---- 75 files changed, 1811 insertions(+), 3326 deletions(-) create mode 100644 internal/testdb/db_helpers.go delete mode 100644 migrations/provider_variants.go diff --git a/.agents/skills/tdd-unit-testing/SKILL.md b/.agents/skills/tdd-unit-testing/SKILL.md index 064134db..1be30ff5 100644 --- a/.agents/skills/tdd-unit-testing/SKILL.md +++ b/.agents/skills/tdd-unit-testing/SKILL.md @@ -26,7 +26,7 @@ description: Write unit tests in Go following Red-Green-Refactor TDD principles. **Handlers**: Create handler struct with UseCase/Service field; return `http.HandlerFunc` from `Handler()` method; test via httptest **Services**: Mock repositories; test business logic and error handling -**Repositories**: Test against real SQLite database (Bun ORM); use test fixtures to set up schema +**Repositories**: Test against real Postgres database (Bun ORM via shared testcontainer); use `NewIntegrationTestDB(t)` helper **Integration tests**: Use fixtures; test plugin routes end-to-end ## Pattern @@ -43,7 +43,7 @@ See [examples/](examples/) for Todos testing patterns: - `test_helpers.go` - MockTodoUseCase and MockTodoService interfaces - `handler_test.go` - Handler struct with UseCase, Handler() method, httptest patterns -- `repository_test.go` - Real SQLite database tests with test fixtures, CRUD operations +- `repository_test.go` - Real Postgres database tests with test fixtures, CRUD operations - `todo_service_test.go` - Service tests with mocked repositories and table-driven tests - `plugin_integration_test.go` - End-to-end route testing with fixtures diff --git a/.agents/skills/tdd-unit-testing/examples/repository_test.go b/.agents/skills/tdd-unit-testing/examples/repository_test.go index 7059218f..819c3a6b 100644 --- a/.agents/skills/tdd-unit-testing/examples/repository_test.go +++ b/.agents/skills/tdd-unit-testing/examples/repository_test.go @@ -2,38 +2,19 @@ package todos_test import ( "context" - "database/sql" "testing" "github.com/stretchr/testify/assert" - "github.com/uptake/go-bun" - "github.com/uptake/go-bun/driver/sqliteshim" + "github.com/uptrace/bun" + + "github.com/Authula/authula/internal/testdb" ) // ================== TEST FIXTURE ================== -// setupTestDB creates an in-memory SQLite database with Bun ORM. +// setupTestDB creates a test Postgres database using the shared testcontainer. func setupTestDB(t *testing.T) bun.IDB { - // Open in-memory SQLite - sqldb, err := sql.Open(sqliteshim.ShimName, "file::memory:?cache=shared") - assert.NoError(t, err) - - // Create Bun database instance - db := bun.NewDB(sqldb) - - t.Cleanup(func() { - db.Close() - }) - - // Register models for schema creation - db.RegisterModel((*Todo)(nil)) - - // Create todos table using Bun schema - ctx := context.Background() - _, err = db.NewCreateTable().Model((*Todo)(nil)).Exec(ctx) - assert.NoError(t, err) - - return db + return testdb.NewIntegrationTestDB(t) } // Todo represents the table structure (Bun model). @@ -82,7 +63,6 @@ func (r *TodoRepository) MarkComplete(ctx context.Context, id string) error { } func randomID() string { - // In real code, use UUID library return "abc123" } @@ -110,8 +90,11 @@ func TestTodoRepository_Create(t *testing.T) { t.Parallel() db := setupTestDB(t) - repo := NewTodoRepository(db) ctx := context.Background() + _, err := db.NewCreateTable().Model((*Todo)(nil)).Exec(ctx) + assert.NoError(t, err) + + repo := NewTodoRepository(db) todoID, err := repo.Create(ctx, tt.title) @@ -173,8 +156,11 @@ func TestTodoRepository_MarkComplete(t *testing.T) { t.Parallel() db := setupTestDB(t) - repo := NewTodoRepository(db) ctx := context.Background() + _, err := db.NewCreateTable().Model((*Todo)(nil)).Exec(ctx) + assert.NoError(t, err) + + repo := NewTodoRepository(db) todoID, err := repo.Create(ctx, tt.title) assert.NoError(t, err) @@ -189,7 +175,6 @@ func TestTodoRepository_MarkComplete(t *testing.T) { } } -// TestTodoRepository_TableDriven shows multiple operations in one test. func TestTodoRepository_TableDriven(t *testing.T) { t.Parallel() @@ -211,7 +196,7 @@ func TestTodoRepository_TableDriven(t *testing.T) { { name: "empty title", title: "", - wantErr: false, // DB allows empty, validation in service layer + wantErr: false, }, } @@ -220,20 +205,20 @@ func TestTodoRepository_TableDriven(t *testing.T) { t.Parallel() db := setupTestDB(t) - repo := NewTodoRepository(db) ctx := context.Background() + _, err := db.NewCreateTable().Model((*Todo)(nil)).Exec(ctx) + assert.NoError(t, err) + + repo := NewTodoRepository(db) - // Act todoID, err := repo.Create(ctx, tt.title) - // Assert if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.NotEmpty(t, todoID) - // Verify retrieval title, _, err := repo.GetByID(ctx, todoID) assert.NoError(t, err) assert.Equal(t, tt.title, title) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f241997e..bf453e20 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -10,8 +10,6 @@ RUN apt update && apt install -y \ curl wget git zip unzip less zsh jq lsof ripgrep ENV HOME="/root" -# Required for some cgo operations such as using certain database drivers like SQLite -ENV CGO_ENABLED=1 # -------------------------------------- # Git diff --git a/Dockerfile b/Dockerfile index 96b06b9d..1f907d7a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # ------------------- FROM golang:1.26.4-alpine AS builder -RUN apk add --no-cache git ca-certificates build-base +RUN apk add --no-cache git ca-certificates WORKDIR /app @@ -12,7 +12,7 @@ RUN go mod download COPY . ./ -RUN CGO_ENABLED=1 GOOS=linux go build \ +RUN GOOS=linux go build \ -ldflags="-s -w" \ -o server ./cmd/main.go diff --git a/Makefile b/Makefile index d13ae0db..a4314f7d 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ APP_NAME=authula BINARY_PATH=./tmp/$(APP_NAME) MIGRATE_CONFIG?=./config.toml MIGRATE_ARGS?= -MIGRATE_CMD=CGO_ENABLED=1 go run ./cmd/migrate +MIGRATE_CMD=go run ./cmd/migrate .PHONY: help build build-exe run dev test clean install setup .PHONY: test-coverage @@ -29,20 +29,20 @@ build-exe: # Build the binary executable run: # Run the application @rm -f ./tmp/$(APP_NAME) - @CGO_ENABLED=1 go run ./cmd/main.go + @go run ./cmd/main.go dev: # Run the application with live reloading using air @rm -f ./tmp/$(APP_NAME) - @CGO_ENABLED=1 ./bin/air --build.cmd "go build -o ./tmp/$(APP_NAME) ./cmd/main.go" --build.entrypoint "./tmp/$(APP_NAME)" + @./bin/air --build.cmd "go build -o ./tmp/$(APP_NAME) ./cmd/main.go" --build.entrypoint "./tmp/$(APP_NAME)" # Test commands test: # Run all tests @echo "Running tests..." - @CGO_ENABLED=1 go test -race -v ./... + @go test -race -v -p 1 ./... test-coverage: # Run tests with coverage report @echo "Running tests with coverage..." - @CGO_ENABLED=1 go test -race -v -coverprofile=coverage.out ./... + @go test -race -v -coverprofile=coverage.out ./... @go tool cover -html=coverage.out -o coverage.html @echo "Coverage report generated: coverage.html" @go tool cover -func=coverage.out | grep total | awk '{print "Total coverage: " $$3}' @@ -97,9 +97,6 @@ quick-check: format vet test # Run quick checks (format, vet, fast tests) ci: clean install check # CI pipeline (clean, install, check) -# Integration testing -integration-test: docker-down docker-up docker-test # Run integration tests with Docker - # Migration commands migrate-help: # Show migration CLI help @$(MIGRATE_CMD) --help diff --git a/README.md b/README.md index 06fb61f8..f8720e5e 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Authula comes with a variety of plugins that provide essential authentication fe - Email & Password: Email-based flows for registration, login, email verification, password reset & email change. - OAuth providers - TOTP: Authenticator app support, backup codes, trusted devices for two-factor authentication -- Multiple database backends (SQLite, PostgreSQL, MySQL) +- PostgreSQL database (primary) - Secondary storage (In-memory, DB, Redis) - Rate limiting - CSRF protection diff --git a/auth.go b/auth.go index 08fb0840..1c19c12a 100644 --- a/auth.go +++ b/auth.go @@ -68,7 +68,7 @@ func New(authConfig *AuthConfig) *Auth { migrationManager := migrationmanager.NewManager(migrator) - if err := migrationManager.RunCore(context.Background(), authConfig.Config.Database.Provider); err != nil { + if err := migrationManager.RunCore(context.Background()); err != nil { panic(fmt.Errorf("failed to run core migrations: %w", err)) } @@ -160,14 +160,14 @@ func (auth *Auth) RunCoreMigrations(ctx context.Context) error { if auth.migrationManager == nil { return fmt.Errorf("migrator not initialized") } - return auth.migrationManager.RunCore(ctx, auth.config.Database.Provider) + return auth.migrationManager.RunCore(ctx) } func (auth *Auth) DropCoreMigrations(ctx context.Context) error { if auth.migrationManager == nil { return fmt.Errorf("migrator not initialized") } - return auth.migrationManager.DropCore(ctx, auth.config.Database.Provider) + return auth.migrationManager.DropCore(ctx) } // registerMiddleware registers all middleware from hooks and plugins diff --git a/bootstrap.go b/bootstrap.go index ea9b5d26..102aad20 100644 --- a/bootstrap.go +++ b/bootstrap.go @@ -21,11 +21,10 @@ func InitLogger(config *models.Config) models.Logger { return internalbootstrap.InitLogger(internalbootstrap.LoggerOptions{Level: config.Logger.Level}) } -// InitDatabase creates a Bun DB connection based on provider +// InitDatabase creates a Bun DB connection func InitDatabase(config *models.Config, logger models.Logger, logLevel string) (bun.IDB, error) { return internalbootstrap.InitDatabase( internalbootstrap.DatabaseOptions{ - Provider: config.Database.Provider, URL: config.Database.URL, MaxOpenConns: config.Database.MaxOpenConns, MaxIdleConns: config.Database.MaxIdleConns, diff --git a/cmd/migrate/main.go b/cmd/migrate/main.go index f8acb649..fd5f999d 100644 --- a/cmd/migrate/main.go +++ b/cmd/migrate/main.go @@ -78,7 +78,7 @@ func runCore(action string) error { switch action { case "up": runtime.logger.Info("running core migrations") - return runtime.manager.RunCore(ctx, runtime.config.Database.Provider) + return runtime.manager.RunCore(ctx) case "down": applied, err := runtime.manager.Migrator().ListApplied(ctx, "") if err != nil { @@ -91,7 +91,7 @@ func runCore(action string) error { } runtime.logger.Info("rolling back core migrations") - return runtime.manager.DropCore(ctx, runtime.config.Database.Provider) + return runtime.manager.DropCore(ctx) default: return fmt.Errorf("unknown core action: %s", action) } @@ -152,10 +152,10 @@ func runPlugins(action, only, except string, includeDisabled bool) error { } runtime.logger.Debug("running plugin migrations") - return runtime.manager.RunPlugins(ctx, runtime.config.Database.Provider, runtime.plugins, selector) + return runtime.manager.RunPlugins(ctx, runtime.plugins, selector) case "down": runtime.logger.Debug("rolling back plugin migrations") - return runtime.manager.DropPlugins(ctx, runtime.config.Database.Provider, runtime.plugins, selector) + return runtime.manager.DropPlugins(ctx, runtime.plugins, selector) default: return fmt.Errorf("unknown plugins action: %s", action) } diff --git a/config/options.go b/config/options.go index e9b98f3c..2e64f242 100644 --- a/config/options.go +++ b/config/options.go @@ -133,9 +133,6 @@ func WithSecret(secret string) ConfigOption { func WithDatabase(config models.DatabaseConfig) ConfigOption { return func(c *models.Config) { - if config.Provider != "" { - c.Database.Provider = config.Provider - } if envValue := os.Getenv(env.EnvDatabaseURL); envValue != "" { c.Database.URL = envValue } else if config.URL != "" { @@ -248,9 +245,6 @@ func WithEventBus(config models.EventBusConfig) ConfigOption { if config.GoChannel != nil { c.EventBus.GoChannel = config.GoChannel } - if config.SQLite != nil { - c.EventBus.SQLite = config.SQLite - } if config.PostgreSQL != nil { c.EventBus.PostgreSQL = config.PostgreSQL } @@ -307,11 +301,6 @@ func validateEventBusConfig(config *models.EventBusConfig) error { return fmt.Errorf("gochannel provider selected but gochannel config is missing") } - case "sqlite": - if config.SQLite == nil { - return fmt.Errorf("sqlite provider selected but sqlite config is missing") - } - case "postgres": if config.PostgreSQL == nil { return fmt.Errorf("postgres provider selected but postgres config is missing") diff --git a/events/constants.go b/events/constants.go index 3d006d2f..3db56a90 100644 --- a/events/constants.go +++ b/events/constants.go @@ -4,7 +4,6 @@ type EventBusProvider string const ( ProviderGoChannel EventBusProvider = "gochannel" - ProviderSQLite EventBusProvider = "sqlite" ProviderPostgres EventBusProvider = "postgres" ProviderRedis EventBusProvider = "redis" ProviderKafka EventBusProvider = "kafka" @@ -18,7 +17,7 @@ func (p EventBusProvider) String() string { func (p EventBusProvider) Valid() bool { switch p { - case ProviderGoChannel, ProviderSQLite, ProviderPostgres, ProviderRedis, ProviderKafka, ProviderNATS, ProviderRabbitMQ: + case ProviderGoChannel, ProviderPostgres, ProviderRedis, ProviderKafka, ProviderNATS, ProviderRabbitMQ: return true } return false diff --git a/go.mod b/go.mod index dfc87094..6fcd2e58 100644 --- a/go.mod +++ b/go.mod @@ -11,10 +11,8 @@ require ( github.com/ThreeDotsLabs/watermill-nats/v2 v2.2.0 github.com/ThreeDotsLabs/watermill-redisstream v1.4.5 github.com/ThreeDotsLabs/watermill-sql/v3 v3.1.0 - github.com/ThreeDotsLabs/watermill-sqlite/wmsqlitezombiezen v0.1.2 github.com/go-chi/chi/v5 v5.3.0 github.com/go-playground/validator/v10 v10.30.3 - github.com/go-sql-driver/mysql v1.10.0 github.com/go-viper/mapstructure/v2 v2.5.0 github.com/gofiber/fiber/v3 v3.3.0 github.com/google/uuid v1.6.0 @@ -22,60 +20,35 @@ require ( github.com/lestrrat-go/jwx/v3 v3.1.1 github.com/lib/pq v1.12.3 github.com/lmittmann/tint v1.1.3 - github.com/mattn/go-sqlite3 v1.14.44 github.com/redis/go-redis/v9 v9.20.0 github.com/resend/resend-go/v3 v3.7.0 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 - github.com/testcontainers/testcontainers-go v0.42.0 - github.com/testcontainers/testcontainers-go/modules/mysql v0.42.0 - github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 github.com/uptrace/bun v1.2.18 - github.com/uptrace/bun/dialect/mysqldialect v1.2.18 github.com/uptrace/bun/dialect/pgdialect v1.2.18 - github.com/uptrace/bun/dialect/sqlitedialect v1.2.18 github.com/uptrace/bun/extra/bundebug v1.2.18 github.com/wneessen/go-mail v0.7.3 golang.org/x/crypto v0.52.0 golang.org/x/oauth2 v0.36.0 - modernc.org/sqlite v1.51.0 - zombiezen.com/go/sqlite v1.4.2 ) require ( cloud.google.com/go/compute/metadata v0.9.0 // indirect - dario.cat/mergo v1.0.2 // indirect - filippo.io/edwards25519 v1.2.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Rican7/retry v0.3.1 // indirect github.com/andybalholm/brotli v1.2.1 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect - github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect - github.com/distribution/reference v0.6.0 // indirect github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 // indirect - github.com/docker/go-connections v0.7.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect github.com/eapache/go-resiliency v1.7.0 // indirect - github.com/ebitengine/purego v0.10.1 // indirect github.com/fatih/color v1.19.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-sql-driver/mysql v1.10.0 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/gofiber/schema v1.7.2 // indirect github.com/gofiber/utils/v2 v2.1.0 // indirect @@ -99,44 +72,23 @@ require ( github.com/lestrrat-go/httprc/v3 v3.0.5 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/lithammer/shortuuid/v3 v3.0.7 // indirect - github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect - github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.22 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/go-archive v0.2.0 // indirect - github.com/moby/moby/api v1.54.2 // indirect - github.com/moby/moby/client v0.4.1 // indirect - github.com/moby/patternmatcher v0.6.1 // indirect - github.com/moby/sys/sequential v0.6.0 // indirect - github.com/moby/sys/user v0.4.0 // indirect - github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.2 // indirect github.com/nats-io/nats.go v1.52.0 // indirect github.com/nats-io/nkeys v0.4.16 // indirect github.com/nats-io/nuid v1.0.1 // indirect - github.com/ncruces/go-strftime v1.0.0 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.1 // indirect github.com/philhofer/fwd v1.2.0 // indirect github.com/pierrec/lz4/v4 v4.1.27 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect github.com/rabbitmq/amqp091-go v1.11.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/segmentio/asm v1.2.1 // indirect - github.com/shirou/gopsutil/v4 v4.26.5 // indirect - github.com/sirupsen/logrus v1.9.4 // indirect - github.com/sony/gobreaker v1.0.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/tinylib/msgp v1.6.4 // indirect - github.com/tklauser/go-sysconf v0.4.0 // indirect - github.com/tklauser/numcpus v0.12.0 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.71.0 // indirect @@ -144,21 +96,15 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/mod v0.36.0 // indirect golang.org/x/net v0.55.0 // indirect golang.org/x/sys v0.45.0 // indirect golang.org/x/text v0.37.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.72.5 // indirect - modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index 0fe89c29..fce9109d 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,11 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= -dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/IBM/sarama v1.50.1 h1:OcXFv571hg+h6N8NL3mFIEdK+80t1m51kf/KZQtbfPA= github.com/IBM/sarama v1.50.1/go.mod h1:+ggHBIXkEU3KXhAm2nCzCu7ohBD1pBIaXCRk1ftPkjY= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Rican7/retry v0.3.1 h1:scY4IbO8swckzoA/11HgBwaZRJEyY9vaNJshcdhp1Mc= github.com/Rican7/retry v0.3.1/go.mod h1:CxSDrhAyXmTMeEuRAnArMu1FHu48vtfjLREWqVl7Vw0= github.com/ThreeDotsLabs/watermill v1.5.2 h1:0ES33Eq1jEsP/pWvtE4n8bE0bs+9Jq7boT7wGBCVY6Q= @@ -28,10 +20,6 @@ github.com/ThreeDotsLabs/watermill-redisstream v1.4.5 h1:SCETqsAYo/CRBb7H3+zWCcS github.com/ThreeDotsLabs/watermill-redisstream v1.4.5/go.mod h1:Da3wqG1OcvHPODjuJcxSCY1O7D4loIZQpVbZ5u94xRo= github.com/ThreeDotsLabs/watermill-sql/v3 v3.1.0 h1:g4uE5Nm3Z6LVB3m+uMgHlN4ne4bDpwf3RJmXYRgMv94= github.com/ThreeDotsLabs/watermill-sql/v3 v3.1.0/go.mod h1:G8/otZYWLTCeYL2Ww3ujQ7gQ/3+jw5Bj0UtyKn7bBjA= -github.com/ThreeDotsLabs/watermill-sqlite/test v0.1.1 h1:bYzMJz8m+fY56z5tfpseTNIl5zjT3fQm+d05KBvuDms= -github.com/ThreeDotsLabs/watermill-sqlite/test v0.1.1/go.mod h1:FZC2Afdhlqp8dtaqHxrSmHqWBC2az3mDbAq6D/fdCT8= -github.com/ThreeDotsLabs/watermill-sqlite/wmsqlitezombiezen v0.1.2 h1:22sRqgmkgOPK917+p17I6yxyeEjPZHDgI2jVx921h4Q= -github.com/ThreeDotsLabs/watermill-sqlite/wmsqlitezombiezen v0.1.2/go.mod h1:hAS4p6c6pXWZicLcD5Fy0KY1sZ3/k9ZSLI1YHs1xPIk= github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= @@ -40,48 +28,20 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= -github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= -github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= -github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= -github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 h1:R2zQhFwSCyyd7L43igYjDrH0wkC/i+QBPELuY0HOu84= github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0/go.mod h1:2MqLKYJfjs3UriXXF9Fd0Qmh/lhxi/6tHXkqtXxyIHc= -github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= -github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA= github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= -github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY= -github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= @@ -93,9 +53,6 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -123,8 +80,6 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -138,8 +93,6 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -158,10 +111,6 @@ github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU= github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= -github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -208,50 +157,18 @@ github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJV github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I= github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= -github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak= -github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= -github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= -github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= -github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= -github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= -github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= -github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= -github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= -github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= -github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= -github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= -github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= -github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= -github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= -github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= -github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= -github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/nats-io/nats.go v1.52.0 h1:n3avV4VBsCgsdwh71TppsTwtv+QdPs7ntSKM8qJLGsc= github.com/nats-io/nats.go v1.52.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno= github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg= github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= -github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= -github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= @@ -260,8 +177,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/rabbitmq/amqp091-go v1.11.0 h1:HxIctVm9Gid/Vtn706necmZ7Wj6pgGI2eqplRbEY8O8= @@ -270,8 +185,6 @@ github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8A github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.20.0 h1:WnQYxLkgO2xiXTCJY0ldIiI8dNqCDlQAG+AtaH7a2a0= github.com/redis/go-redis/v9 v9.20.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/resend/resend-go/v3 v3.7.0 h1:puE9z+Re8i+regKcvPF8flIiBrKZcxJhbkbMQcwl0OE= github.com/resend/resend-go/v3 v3.7.0/go.mod h1:iI7VA0NoGjWvsNii5iNC5Dy0llsI3HncXPejhniYzwE= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -281,12 +194,6 @@ github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/shamaton/msgpack/v3 v3.1.2 h1:d5gWAIyMU4M0WgDjz6IFSCuXJUA2dFwRHBpDclE8CLw= github.com/shamaton/msgpack/v3 v3.1.2/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc= -github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM= -github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= -github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ= -github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -297,35 +204,20 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= -github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= -github.com/testcontainers/testcontainers-go/modules/mysql v0.42.0 h1:Yhv1k7vDpyzZePntg5R5Oj4ZMCyWpAfpJeRu1ROsgiU= -github.com/testcontainers/testcontainers-go/modules/mysql v0.42.0/go.mod h1:Z7SCTuiZlghAdRjkv3Ir0iXJKC2T2avbtxLR0DRe+ng= -github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 h1:GCbb1ndrF7OTDiIvxXyItaDab4qkzTFJ48LKFdM7EIo= -github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0/go.mod h1:IRPBaI8jXdrNfD0e4Zm7Fbcgaz5shKxOQv4axiL09xs= github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ= github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= -github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= -github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI= -github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4= -github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/uptrace/bun v1.2.18 h1:3HnRcMfS6OBPMG1eSOzlbFJ/X/AyMEJb7rMxE6VQvDU= github.com/uptrace/bun v1.2.18/go.mod h1:wNltaKJk4JtOt4SG5I5zmA7v0/Mzjh1+/S906Rayd3Y= -github.com/uptrace/bun/dialect/mysqldialect v1.2.18 h1:w+3iuWa4cVmsXXt8w28A0+Ikve77AU0tiBWG6UvGvM8= -github.com/uptrace/bun/dialect/mysqldialect v1.2.18/go.mod h1:FhJEK620SM9HJ9fx0/IHT7k1cpn2+6MmtKvNptWezPY= github.com/uptrace/bun/dialect/pgdialect v1.2.18 h1:IZ6nM2+OYrL8lkEAy7UkSEZvoa3vluTAUlZfPtlRB2k= github.com/uptrace/bun/dialect/pgdialect v1.2.18/go.mod h1:Tqdf4QP1okrGYpXfodXvCOK6Ob1OOTwSaoAzCgBB3IU= -github.com/uptrace/bun/dialect/sqlitedialect v1.2.18 h1:Z33SY/U++XK9uGWqS4h8OZVxfCXguIG+sU9cYq2PGFQ= -github.com/uptrace/bun/dialect/sqlitedialect v1.2.18/go.mod h1:1MVOS/Ncy4FZbkJcgUFH6OqYoQinYNjkEwsmNQEXz2A= github.com/uptrace/bun/extra/bundebug v1.2.18 h1:5cgkqdvhpSHIEONazSytm4RWYFneNtcznaWLt6r8m4M= github.com/uptrace/bun/extra/bundebug v1.2.18/go.mod h1:M+U9YJVJcmk0RrszCb2Q1oskJiJ0LuC44FxDhZLP1ws= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -347,22 +239,14 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -376,8 +260,6 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58 golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -393,22 +275,16 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -419,8 +295,6 @@ golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -436,37 +310,3 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= -gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY= -modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= -modernc.org/ccgo/v4 v4.34.2 h1:mxsy2FdrB6+qG3NfXefz1AmWv0ehOSDO4jxgxd7h9yo= -modernc.org/ccgo/v4 v4.34.2/go.mod h1:1L7us56+kAKu04p25EATpmBBvhbcqqZ85ibqWVwVgog= -modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= -modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= -modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= -modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc= -modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= -modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= -modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.72.5 h1:m2OGx9Ser1VvTS4Z9ZJlWs+CBMxutLaTiAWkNz+NB9U= -modernc.org/libc v1.72.5/go.mod h1:np0N7KDJ7eUtMZmOqVZNldrZyG+DHLl2B5pg8Hbar3U= -modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= -modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= -modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= -modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= -modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= -modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.51.0 h1:aH/MMSoayAIhozZ7uJbVTT9QO/VhzBf0J9tymmmuC/U= -modernc.org/sqlite v1.51.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM= -modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= -modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= -pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= -zombiezen.com/go/sqlite v1.4.2 h1:KZXLrBuJ7tKNEm+VJcApLMeQbhmAUOKA5VWS93DfFRo= -zombiezen.com/go/sqlite v1.4.2/go.mod h1:5Kd4taTAD4MkBzT25mQ9uaAlLjyR0rFhsR6iINO70jc= diff --git a/internal/bootstrap/database.go b/internal/bootstrap/database.go index f9a933c1..d92bae36 100644 --- a/internal/bootstrap/database.go +++ b/internal/bootstrap/database.go @@ -4,18 +4,13 @@ import ( "database/sql" "fmt" "os" - "path/filepath" "runtime" "time" - _ "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" - _ "github.com/mattn/go-sqlite3" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/mysqldialect" "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/dialect/sqlitedialect" "github.com/uptrace/bun/extra/bundebug" "github.com/Authula/authula/env" @@ -23,7 +18,6 @@ import ( ) type DatabaseOptions struct { - Provider string URL string MaxOpenConns int MaxIdleConns int @@ -31,10 +25,6 @@ type DatabaseOptions struct { } func InitDatabase(opts DatabaseOptions, logger models.Logger, logLevel string) (bun.IDB, error) { - if opts.Provider == "" { - return nil, fmt.Errorf("database provider must be specified") - } - databaseURL := os.Getenv(env.EnvDatabaseURL) if databaseURL == "" { if opts.URL == "" { @@ -43,55 +33,13 @@ func InitDatabase(opts DatabaseOptions, logger models.Logger, logLevel string) ( databaseURL = opts.URL } - var ( - sqlDB *sql.DB - db *bun.DB - err error - ) - - switch opts.Provider { - case "sqlite": - isMemory := databaseURL == ":memory:" - - if !isMemory { - if !filepath.IsAbs(databaseURL) { - cwd, _ := os.Getwd() - databaseURL = filepath.Join(cwd, databaseURL) - } - - dbDir := filepath.Dir(databaseURL) - if err := os.MkdirAll(dbDir, 0755); err != nil { - return nil, fmt.Errorf("failed to create database directory: %w", err) - } - } - - sqlDB, err = sql.Open("sqlite3", databaseURL) - if err != nil { - return nil, err - } - - db = bun.NewDB(sqlDB, sqlitedialect.New()) - - case "postgres": - sqlDB, err = sql.Open("postgres", databaseURL) - if err != nil { - return nil, err - } - - db = bun.NewDB(sqlDB, pgdialect.New()) - - case "mysql": - sqlDB, err = sql.Open("mysql", databaseURL) - if err != nil { - return nil, err - } - - db = bun.NewDB(sqlDB, mysqldialect.New()) - - default: - return nil, fmt.Errorf("unsupported database provider: %s", opts.Provider) + sqlDB, err := sql.Open("postgres", databaseURL) + if err != nil { + return nil, err } + db := bun.NewDB(sqlDB, pgdialect.New()) + configurePool(sqlDB, opts) enableDebugging(db, logLevel) diff --git a/internal/events/watermill_providers.go b/internal/events/watermill_providers.go index 47375bd9..f97e3c8d 100644 --- a/internal/events/watermill_providers.go +++ b/internal/events/watermill_providers.go @@ -5,7 +5,6 @@ import ( "database/sql" "fmt" "os" - "path/filepath" "strings" "time" @@ -16,11 +15,9 @@ import ( "github.com/ThreeDotsLabs/watermill-nats/v2/pkg/nats" "github.com/ThreeDotsLabs/watermill-redisstream/pkg/redisstream" watermillSQL "github.com/ThreeDotsLabs/watermill-sql/v3/pkg/sql" - "github.com/ThreeDotsLabs/watermill-sqlite/wmsqlitezombiezen" "github.com/ThreeDotsLabs/watermill/pubsub/gochannel" _ "github.com/lib/pq" "github.com/redis/go-redis/v9" - "zombiezen.com/go/sqlite/sqlitex" "github.com/Authula/authula/env" "github.com/Authula/authula/events" @@ -48,8 +45,6 @@ func InitWatermillProvider(config *models.EventBusConfig, logger watermill.Logge return initNATS(logger, config.NATS) case events.ProviderPostgres: return initPostgres(logger, config.PostgreSQL) - case events.ProviderSQLite: - return initSQLite(logger, config.SQLite) default: return nil, fmt.Errorf("unsupported event bus provider: %s", config.Provider) } @@ -74,61 +69,6 @@ func initGoChannel(logger watermill.LoggerAdapter, config *models.GoChannelConfi return NewWatermillPubSub(pubSub, pubSub), nil } -// initSQLite initializes a SQLite provider using the ZombieZen driver -func initSQLite(logger watermill.LoggerAdapter, config *models.SQLiteConfig) (models.PubSub, error) { - dbPath := "events.db" - if config != nil && config.DBPath != "" { - dbPath = config.DBPath - } - - // Create directory if it doesn't exist - dir := filepath.Dir(dbPath) - if dir != "." && dir != "" { - if err := os.MkdirAll(dir, 0755); err != nil { - return nil, fmt.Errorf("failed to create directory for sqlite database: %w", err) - } - } - - // NewSubscriber expects a connection DSN string - subscriber, err := wmsqlitezombiezen.NewSubscriber( - dbPath, - wmsqlitezombiezen.SubscriberOptions{ - InitializeSchema: true, - Logger: logger, - }, - ) - if err != nil { - return nil, fmt.Errorf("failed to create sqlite subscriber: %w", err) - } - - // For the publisher, we need to get a connection from the pool - // Create a pool to get a connection for the publisher - pool, err := sqlitex.NewPool("file:"+dbPath, sqlitex.PoolOptions{ - PoolSize: 5, - }) - if err != nil { - return nil, fmt.Errorf("failed to create sqlite connection pool: %w", err) - } - - conn, err := pool.Take(context.Background()) - if err != nil { - return nil, fmt.Errorf("failed to get sqlite connection from pool: %w", err) - } - - publisher, err := wmsqlitezombiezen.NewPublisher( - conn, - wmsqlitezombiezen.PublisherOptions{ - Logger: logger, - }, - ) - if err != nil { - pool.Put(conn) - return nil, fmt.Errorf("failed to create sqlite publisher: %w", err) - } - - return NewWatermillPubSub(publisher, subscriber), nil -} - // initPostgres initializes a Postgres SQL provider func initPostgres(logger watermill.LoggerAdapter, config *models.PostgreSQLConfig) (models.PubSub, error) { url := os.Getenv("POSTGRES_URL") diff --git a/internal/events/watermill_providers_test.go b/internal/events/watermill_providers_test.go index db9a4d65..eb5d2231 100644 --- a/internal/events/watermill_providers_test.go +++ b/internal/events/watermill_providers_test.go @@ -1,345 +1,152 @@ package events import ( - "os" "testing" - "github.com/Authula/authula/models" "github.com/ThreeDotsLabs/watermill" -) - -func TestInitWatermillProvider_GoChannel(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "gochannel", - GoChannel: &models.GoChannelConfig{BufferSize: 100}, - } - - pubsub, err := InitWatermillProvider(config, logger) - if err != nil { - t.Fatalf("failed to initialize gochannel provider: %v", err) - } - defer func() { - if err := pubsub.Close(); err != nil { - t.Errorf("failed to close pubsub: %v", err) - } - }() - - if pubsub == nil { - t.Fatal("expected pubsub to be non-nil") - } -} - -func TestInitWatermillProvider_GoChannel_DefaultBufferSize(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "gochannel", - GoChannel: &models.GoChannelConfig{}, - } - - pubsub, err := InitWatermillProvider(config, logger) - if err != nil { - t.Fatalf("failed to initialize gochannel provider with default buffer: %v", err) - } - defer func() { - if err := pubsub.Close(); err != nil { - t.Errorf("failed to close pubsub: %v", err) - } - }() - - if pubsub == nil { - t.Fatal("expected pubsub to be non-nil") - } -} - -func TestInitWatermillProvider_GoChannel_NilConfig(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "gochannel", - GoChannel: nil, - } - - pubsub, err := InitWatermillProvider(config, logger) - if err != nil { - t.Fatalf("failed to initialize gochannel provider with nil config: %v", err) - } - defer func() { - if err := pubsub.Close(); err != nil { - t.Errorf("failed to close pubsub: %v", err) - } - }() - - if pubsub == nil { - t.Fatal("expected pubsub to be non-nil") - } -} - -func TestInitWatermillProvider_UnsupportedProvider(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "unsupported", - } - - _, err := InitWatermillProvider(config, logger) - if err == nil { - t.Fatal("expected error for unsupported provider, got nil") - } -} - -func TestInitWatermillProvider_Redis_MissingURL(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "redis", - Redis: &models.RedisConfig{}, - } - - _, err := InitWatermillProvider(config, logger) - if err == nil { - t.Fatal("expected error for missing redis URL, got nil") - } -} - -func TestInitWatermillProvider_Redis_NilConfig(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "redis", - Redis: nil, - } - - _, err := InitWatermillProvider(config, logger) - if err == nil { - t.Fatal("expected error for missing redis config, got nil") - } -} - -func TestInitWatermillProvider_Kafka_MissingBrokers(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "kafka", - Kafka: &models.KafkaConfig{}, - } - - _, err := InitWatermillProvider(config, logger) - if err == nil { - t.Fatal("expected error for missing kafka brokers, got nil") - } -} - -func TestInitWatermillProvider_Kafka_NilConfig(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "kafka", - Kafka: nil, - } - - _, err := InitWatermillProvider(config, logger) - if err == nil { - t.Fatal("expected error for missing kafka config, got nil") - } -} - -func TestInitWatermillProvider_NATS_MissingURL(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "nats", - NATS: &models.NatsConfig{}, - } - - _, err := InitWatermillProvider(config, logger) - if err == nil { - t.Fatal("expected error for missing nats URL, got nil") - } -} - -func TestInitWatermillProvider_NATS_NilConfig(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "nats", - NATS: nil, - } - - _, err := InitWatermillProvider(config, logger) - if err == nil { - t.Fatal("expected error for missing nats config, got nil") - } -} - -func TestInitWatermillProvider_Postgres_MissingURL(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "postgres", - PostgreSQL: &models.PostgreSQLConfig{}, - } - - _, err := InitWatermillProvider(config, logger) - if err == nil { - t.Fatal("expected error for missing postgres URL, got nil") - } -} - -func TestInitWatermillProvider_Postgres_NilConfig(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "postgres", - PostgreSQL: nil, - } - - _, err := InitWatermillProvider(config, logger) - if err == nil { - t.Fatal("expected error for missing postgres config, got nil") - } -} - -func TestInitWatermillProvider_SQLite_DefaultPath(t *testing.T) { - if os.Getenv("AUTHULA_TEST_DB") != "" && os.Getenv("AUTHULA_TEST_DB") != "sqlite" { - t.Skip("skipping SQLite test when AUTHULA_TEST_DB is set to a different provider") - } - - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "sqlite", - SQLite: nil, // should default to events.db - } - - pubsub, err := InitWatermillProvider(config, logger) - if err != nil { - t.Fatalf("failed to initialize sqlite provider with default path: %v", err) - } - defer func() { - if err := pubsub.Close(); err != nil { - t.Errorf("failed to close pubsub: %v", err) - } - }() - defer func() { - if err := os.Remove("events.db"); err != nil && !os.IsNotExist(err) { - t.Errorf("failed to remove events.db: %v", err) - } - if err := os.Remove("events.db-shm"); err != nil && !os.IsNotExist(err) { - t.Errorf("failed to remove events.db-shm: %v", err) - } - if err := os.Remove("events.db-wal"); err != nil && !os.IsNotExist(err) { - t.Errorf("failed to remove events.db-wal: %v", err) - } - }() - - if pubsub == nil { - t.Fatal("expected pubsub to be non-nil") - } -} + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" -func TestInitWatermillProvider_SQLite_EmptyPath(t *testing.T) { - if os.Getenv("AUTHULA_TEST_DB") != "" && os.Getenv("AUTHULA_TEST_DB") != "sqlite" { - t.Skip("skipping SQLite test when AUTHULA_TEST_DB is set to a different provider") - } - - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "sqlite", - SQLite: &models.SQLiteConfig{DBPath: ""}, // should default to events.db - } - - pubsub, err := InitWatermillProvider(config, logger) - if err != nil { - t.Fatalf("failed to initialize sqlite provider with empty path: %v", err) - } - defer func() { - if err := pubsub.Close(); err != nil { - t.Errorf("failed to close pubsub: %v", err) - } - }() - defer func() { - if err := os.Remove("events.db"); err != nil && !os.IsNotExist(err) { - t.Errorf("failed to remove events.db: %v", err) - } - if err := os.Remove("events.db-shm"); err != nil && !os.IsNotExist(err) { - t.Errorf("failed to remove events.db-shm: %v", err) - } - if err := os.Remove("events.db-wal"); err != nil && !os.IsNotExist(err) { - t.Errorf("failed to remove events.db-wal: %v", err) - } - }() - - if pubsub == nil { - t.Fatal("expected pubsub to be non-nil") - } -} - -func TestInitWatermillProvider_SQLite_CustomPath(t *testing.T) { - if os.Getenv("AUTHULA_TEST_DB") != "" && os.Getenv("AUTHULA_TEST_DB") != "sqlite" { - t.Skip("skipping SQLite test when AUTHULA_TEST_DB is set to a different provider") - } - - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "sqlite", - SQLite: &models.SQLiteConfig{DBPath: "/tmp/test_events.db"}, - } - - pubsub, err := InitWatermillProvider(config, logger) - if err != nil { - t.Fatalf("failed to initialize sqlite provider with custom path: %v", err) - } - defer func() { - if err := pubsub.Close(); err != nil { - t.Errorf("failed to close pubsub: %v", err) - } - }() - defer func() { - if err := os.Remove("/tmp/test_events.db"); err != nil && !os.IsNotExist(err) { - t.Errorf("failed to remove /tmp/test_events.db: %v", err) - } - if err := os.Remove("/tmp/test_events.db-shm"); err != nil && !os.IsNotExist(err) { - t.Errorf("failed to remove /tmp/test_events.db-shm: %v", err) - } - if err := os.Remove("/tmp/test_events.db-wal"); err != nil && !os.IsNotExist(err) { - t.Errorf("failed to remove /tmp/test_events.db-wal: %v", err) - } - }() - - if pubsub == nil { - t.Fatal("expected pubsub to be non-nil") - } -} - -func TestInitWatermillProvider_RabbitMQ_MissingURL(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "rabbitmq", - RabbitMQ: &models.RabbitMQConfig{}, - } - - _, err := InitWatermillProvider(config, logger) - if err == nil { - t.Fatal("expected error for missing rabbitmq URL, got nil") - } -} - -func TestInitWatermillProvider_RabbitMQ_NilConfig(t *testing.T) { - logger := watermill.NewStdLogger(false, false) - - config := &models.EventBusConfig{ - Provider: "rabbitmq", - RabbitMQ: nil, - } + "github.com/Authula/authula/models" +) - _, err := InitWatermillProvider(config, logger) - if err == nil { - t.Fatal("expected error for missing rabbitmq config, got nil") +func TestInitWatermillProvider_Success(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + config *models.EventBusConfig + }{ + { + name: "gochannel with buffer", + config: &models.EventBusConfig{ + Provider: "gochannel", + GoChannel: &models.GoChannelConfig{BufferSize: 100}, + }, + }, + { + name: "gochannel default buffer", + config: &models.EventBusConfig{ + Provider: "gochannel", + GoChannel: &models.GoChannelConfig{}, + }, + }, + { + name: "gochannel nil config", + config: &models.EventBusConfig{ + Provider: "gochannel", + GoChannel: nil, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + logger := watermill.NewStdLogger(false, false) + pubsub, err := InitWatermillProvider(tt.config, logger) + + require.NoError(t, err) + require.NotNil(t, pubsub) + require.NoError(t, pubsub.Close()) + }) + } +} + +func TestInitWatermillProvider_Error(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + config *models.EventBusConfig + }{ + { + name: "unsupported provider", + config: &models.EventBusConfig{ + Provider: "unsupported", + }, + }, + { + name: "redis missing URL", + config: &models.EventBusConfig{ + Provider: "redis", + Redis: &models.RedisConfig{}, + }, + }, + { + name: "redis nil config", + config: &models.EventBusConfig{ + Provider: "redis", + Redis: nil, + }, + }, + { + name: "kafka missing brokers", + config: &models.EventBusConfig{ + Provider: "kafka", + Kafka: &models.KafkaConfig{}, + }, + }, + { + name: "kafka nil config", + config: &models.EventBusConfig{ + Provider: "kafka", + Kafka: nil, + }, + }, + { + name: "nats missing URL", + config: &models.EventBusConfig{ + Provider: "nats", + NATS: &models.NatsConfig{}, + }, + }, + { + name: "nats nil config", + config: &models.EventBusConfig{ + Provider: "nats", + NATS: nil, + }, + }, + { + name: "postgres missing URL", + config: &models.EventBusConfig{ + Provider: "postgres", + PostgreSQL: &models.PostgreSQLConfig{}, + }, + }, + { + name: "postgres nil config", + config: &models.EventBusConfig{ + Provider: "postgres", + PostgreSQL: nil, + }, + }, + { + name: "rabbitmq missing URL", + config: &models.EventBusConfig{ + Provider: "rabbitmq", + RabbitMQ: &models.RabbitMQConfig{}, + }, + }, + { + name: "rabbitmq nil config", + config: &models.EventBusConfig{ + Provider: "rabbitmq", + RabbitMQ: nil, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + logger := watermill.NewStdLogger(false, false) + _, err := InitWatermillProvider(tt.config, logger) + + assert.Error(t, err) + }) } } diff --git a/internal/migrationmanager/manager.go b/internal/migrationmanager/manager.go index 3a2f96d3..87046a5e 100644 --- a/internal/migrationmanager/manager.go +++ b/internal/migrationmanager/manager.go @@ -30,13 +30,13 @@ func (m *Manager) Migrator() *migrationsmodule.Migrator { return m.migrator } -// RunCore executes the core migrations for the configured provider. -func (m *Manager) RunCore(ctx context.Context, provider string) error { +// RunCore executes the core migrations. +func (m *Manager) RunCore(ctx context.Context) error { if err := m.requireMigrator(); err != nil { return err } - set, err := migrationsmodule.CoreMigrationSet(provider) + set, err := migrationsmodule.CoreMigrationSet() if err != nil { return err } @@ -45,12 +45,12 @@ func (m *Manager) RunCore(ctx context.Context, provider string) error { } // DropCore rolls back all core migrations. -func (m *Manager) DropCore(ctx context.Context, provider string) error { +func (m *Manager) DropCore(ctx context.Context) error { if err := m.requireMigrator(); err != nil { return err } - set, err := migrationsmodule.CoreMigrationSet(provider) + set, err := migrationsmodule.CoreMigrationSet() if err != nil { return err } @@ -59,12 +59,12 @@ func (m *Manager) DropCore(ctx context.Context, provider string) error { } // RunPlugins executes migrations for all selected plugins. -func (m *Manager) RunPlugins(ctx context.Context, provider string, plugins []models.Plugin, selector PluginSelector) error { +func (m *Manager) RunPlugins(ctx context.Context, plugins []models.Plugin, selector PluginSelector) error { if err := m.requireMigrator(); err != nil { return err } - sets := m.PlanPlugins(provider, plugins, selector) + sets := m.PlanPlugins(plugins, selector) if len(sets) == 0 { return nil } @@ -73,12 +73,12 @@ func (m *Manager) RunPlugins(ctx context.Context, provider string, plugins []mod } // DropPlugins rolls back migrations for all selected plugins. -func (m *Manager) DropPlugins(ctx context.Context, provider string, plugins []models.Plugin, selector PluginSelector) error { +func (m *Manager) DropPlugins(ctx context.Context, plugins []models.Plugin, selector PluginSelector) error { if err := m.requireMigrator(); err != nil { return err } - sets := m.PlanPlugins(provider, plugins, selector) + sets := m.PlanPlugins(plugins, selector) if len(sets) == 0 { return nil } @@ -87,7 +87,7 @@ func (m *Manager) DropPlugins(ctx context.Context, provider string, plugins []mo } // PlanPlugins builds migration sets for plugins matching the selector. -func (m *Manager) PlanPlugins(provider string, plugins []models.Plugin, selector PluginSelector) []migrationsmodule.MigrationSet { +func (m *Manager) PlanPlugins(plugins []models.Plugin, selector PluginSelector) []migrationsmodule.MigrationSet { if len(plugins) == 0 { return nil } @@ -103,7 +103,7 @@ func (m *Manager) PlanPlugins(provider string, plugins []models.Plugin, selector continue } - migrations := migratable.Migrations(provider) + migrations := migratable.Migrations() if len(migrations) == 0 { continue } diff --git a/internal/plugins/plugin_registry.go b/internal/plugins/plugin_registry.go index 8a7300c8..f1b65b45 100644 --- a/internal/plugins/plugin_registry.go +++ b/internal/plugins/plugin_registry.go @@ -110,7 +110,6 @@ func (r *PluginRegistry) RunMigrations(ctx context.Context) error { return fmt.Errorf("config provider returned nil config") } - dbProvider := cfg.Database.Provider selector := func(plugin models.Plugin) bool { pluginID := plugin.Metadata().ID if !util.IsPluginEnabled(cfg, pluginID) { @@ -120,7 +119,7 @@ func (r *PluginRegistry) RunMigrations(ctx context.Context) error { return true } - return r.migrationManager.RunPlugins(ctx, dbProvider, r.plugins, selector) + return r.migrationManager.RunPlugins(ctx, r.plugins, selector) } // DropMigrations rolls back migrations for all registered plugins regardless of enablement state. @@ -134,7 +133,7 @@ func (r *PluginRegistry) DropMigrations(ctx context.Context) error { return fmt.Errorf("config provider returned nil config") } - return r.migrationManager.DropPlugins(ctx, cfg.Database.Provider, r.plugins, nil) + return r.migrationManager.DropPlugins(ctx, r.plugins, nil) } func (r *PluginRegistry) Plugins() []models.Plugin { diff --git a/internal/repositories/bun_session_repository_test.go b/internal/repositories/bun_session_repository_test.go index 382da6e3..6c5c6e66 100644 --- a/internal/repositories/bun_session_repository_test.go +++ b/internal/repositories/bun_session_repository_test.go @@ -2,28 +2,18 @@ package repositories import ( "context" - "database/sql" "fmt" "testing" "time" - _ "github.com/mattn/go-sqlite3" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/sqlitedialect" + "github.com/Authula/authula/internal/testdb" "github.com/Authula/authula/models" ) -// Helper function to create an in-memory SQLite database for testing func newTestSessionDB(t *testing.T) bun.IDB { - sqldb, err := sql.Open("sqlite3", ":memory:") - if err != nil { - t.Fatalf("failed to create test database: %v", err) - } - - db := bun.NewDB(sqldb, sqlitedialect.New()) - - // Create the Session table + db := testdb.NewIntegrationTestDB(t) ctx := context.Background() if _, err := db.NewCreateTable().Model(&models.Session{}).Exec(ctx); err != nil { t.Fatalf("failed to create Session table: %v", err) diff --git a/internal/testdb/db_helpers.go b/internal/testdb/db_helpers.go new file mode 100644 index 00000000..e82934ad --- /dev/null +++ b/internal/testdb/db_helpers.go @@ -0,0 +1,169 @@ +package testdb + +import ( + "context" + "crypto/rand" + "database/sql" + "encoding/hex" + "fmt" + "os/exec" + "strings" + "sync" + "testing" + "time" + + _ "github.com/lib/pq" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" +) + +const containerName = "authula-test-pg" + +var ( + mu sync.Mutex + connString string +) + +func randomHex() string { + b := make([]byte, 8) + _, _ = rand.Read(b) + return hex.EncodeToString(b) +} + +func waitForDB(sqlDB *sql.DB) error { + var lastErr error + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + for range 60 { + if err := sqlDB.PingContext(ctx); err == nil { + return nil + } else { + lastErr = err + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(500 * time.Millisecond): + } + } + return lastErr +} + +func ensureContainer() string { + // Check if already running + cmd := exec.Command("docker", "ps", + "--filter", fmt.Sprintf("name=%s", containerName), + "--filter", "status=running", + "--format", "{{.ID}}") + out, _ := cmd.Output() + if strings.TrimSpace(string(out)) != "" { + // Container exists — get its IP + cmd2 := exec.Command("docker", "inspect", + "-f", `{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}`, + containerName) + ipOut, _ := cmd2.Output() + ip := strings.TrimSpace(string(ipOut)) + if ip != "" { + return fmt.Sprintf("postgres://authula:authula@%s:5432/authula?sslmode=disable", ip) + } + } + + // Remove any leftover container + _ = exec.Command("docker", "rm", "-f", containerName).Run() + + // Start new container + cmd = exec.Command("docker", "run", "-d", + "--name", containerName, + "--label", "authula-test-pg=true", + "-e", "POSTGRES_DB=authula", + "-e", "POSTGRES_USER=authula", + "-e", "POSTGRES_PASSWORD=authula", + "postgres:18-alpine", + "-c", "max_connections=200") + out, err := cmd.Output() + if err != nil { + panic(fmt.Errorf("failed to start postgres container: %w\n%s", err, string(out))) + } + + cid := strings.TrimSpace(string(out)) + + // Wait for container to be running + for range 30 { + cmd = exec.Command("docker", "inspect", "-f", "{{.State.Status}}", cid) + status, _ := cmd.Output() + if strings.TrimSpace(string(status)) == "running" { + break + } + time.Sleep(500 * time.Millisecond) + } + + // Get container IP on the Docker bridge network + cmd = exec.Command("docker", "inspect", + "-f", `{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}`, cid) + out, err = cmd.Output() + if err != nil { + panic(fmt.Errorf("failed to get container ip: %w", err)) + } + + ip := strings.TrimSpace(string(out)) + return fmt.Sprintf("postgres://authula:authula@%s:5432/authula?sslmode=disable", ip) +} + +func getConnectionString() string { + mu.Lock() + defer mu.Unlock() + + if connString != "" { + // Verify cached DSN is still valid + sqlDB, err := sql.Open("postgres", connString) + if err == nil { + if waitForDB(sqlDB) == nil { + sqlDB.Close() + return connString + } + sqlDB.Close() + } + connString = "" + } + + dsn := ensureContainer() + connString = dsn + return dsn +} + +func NewIntegrationTestDB(t *testing.T, migrateFns ...func(bun.IDB)) *bun.DB { + t.Helper() + + dsn := getConnectionString() + + schema := "test_" + randomHex() + + sqlDB, err := sql.Open("postgres", dsn) + if err != nil { + t.Fatalf("failed to open postgres db: %v", err) + } + + if _, err := sqlDB.Exec("CREATE SCHEMA " + schema); err != nil { + sqlDB.Close() + t.Fatalf("failed to create schema %s: %v", schema, err) + } + + if _, err := sqlDB.Exec("SET search_path TO " + schema + ", public"); err != nil { + sqlDB.Close() + t.Fatalf("failed to set search_path: %v", err) + } + + db := bun.NewDB(sqlDB, pgdialect.New()) + db.SetMaxOpenConns(1) + + for _, fn := range migrateFns { + fn(db) + } + + t.Cleanup(func() { + _, _ = sqlDB.Exec("DROP SCHEMA IF EXISTS " + schema + " CASCADE") + _ = sqlDB.Close() + }) + + return db +} diff --git a/internal/tests/base_integration_fixture.go b/internal/tests/base_integration_fixture.go index e95bb338..a4de2fdb 100644 --- a/internal/tests/base_integration_fixture.go +++ b/internal/tests/base_integration_fixture.go @@ -22,7 +22,6 @@ type BaseTestFixture struct { T *testing.T DB *bun.DB Config *models.Config - Provider string ServiceRegistry models.ServiceRegistry UserService coreservices.UserService AccountService coreservices.AccountService @@ -35,12 +34,11 @@ type BaseTestFixture struct { func NewBaseTestFixture(t *testing.T, options ...config.ConfigOption) *BaseTestFixture { t.Helper() - db, provider := NewIntegrationTestDBFromEnv(t) + db := NewIntegrationTestDB(t) defaultOptions := []config.ConfigOption{ config.WithBasePath("/auth"), config.WithSecret("integration-test-secret"), - config.WithDatabase(models.DatabaseConfig{Provider: provider}), config.WithEventBus(models.EventBusConfig{Provider: events.ProviderGoChannel}), } @@ -69,7 +67,6 @@ func NewBaseTestFixture(t *testing.T, options ...config.ConfigOption) *BaseTestF T: t, DB: db, Config: cfg, - Provider: provider, ServiceRegistry: serviceRegistry, UserService: userService, AccountService: accountService, diff --git a/internal/tests/db_helpers.go b/internal/tests/db_helpers.go index 29ce62e5..382ae4f6 100644 --- a/internal/tests/db_helpers.go +++ b/internal/tests/db_helpers.go @@ -1,216 +1,14 @@ package tests import ( - "context" - "database/sql" - "fmt" - "os" - "strings" "testing" - "time" - _ "github.com/go-sql-driver/mysql" - _ "github.com/lib/pq" - _ "github.com/mattn/go-sqlite3" - "github.com/testcontainers/testcontainers-go" - tcmysql "github.com/testcontainers/testcontainers-go/modules/mysql" - tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/mysqldialect" - "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/dialect/sqlitedialect" - _ "modernc.org/sqlite" -) - -func NewSQLiteIntegrationDB(t *testing.T) *bun.DB { - t.Helper() - - dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", strings.ReplaceAll(t.Name(), "/", "_")) - sqlDB, err := openSQLiteForTests(dsn) - if err != nil { - t.Fatalf("failed to open sqlite db: %v", err) - } - t.Cleanup(func() { _ = sqlDB.Close() }) - - db := bun.NewDB(sqlDB, sqlitedialect.New()) - t.Cleanup(func() { _ = db.Close() }) - - return db -} - -func NewPostgresIntegrationDB(t *testing.T) *bun.DB { - t.Helper() - ctx := context.Background() - - container, err := runPostgresContainer(ctx) - if err != nil { - if isDockerUnavailableError(err) { - t.Skipf("skipping postgres integration DB setup: %v", err) - return nil - } - t.Fatalf("failed to start postgres testcontainer: %v", err) - } - t.Cleanup(func() { - _ = testcontainers.TerminateContainer(container) - }) - - dsn, err := container.ConnectionString(ctx, "sslmode=disable") - if err != nil { - t.Fatalf("failed to get postgres connection string: %v", err) - } - - sqlDB, err := sql.Open("postgres", dsn) - if err != nil { - t.Fatalf("failed to open postgres db: %v", err) - } - if err := waitForDB(ctx, sqlDB, 30); err != nil { - _ = sqlDB.Close() - t.Fatalf("failed waiting for postgres db readiness: %v", err) - } - t.Cleanup(func() { _ = sqlDB.Close() }) - - db := bun.NewDB(sqlDB, pgdialect.New()) - t.Cleanup(func() { _ = db.Close() }) - - return db -} - -func NewMySQLIntegrationDB(t *testing.T) *bun.DB { - t.Helper() - ctx := context.Background() - - container, err := runMySQLContainer(ctx) - if err != nil { - if isDockerUnavailableError(err) { - t.Skipf("skipping mysql integration DB setup: %v", err) - return nil - } - t.Fatalf("failed to start mysql testcontainer: %v", err) - } - t.Cleanup(func() { - _ = testcontainers.TerminateContainer(container) - }) - - dsn, err := container.ConnectionString(ctx, "parseTime=true") - if err != nil { - t.Fatalf("failed to get mysql connection string: %v", err) - } - - sqlDB, err := sql.Open("mysql", dsn) - if err != nil { - t.Fatalf("failed to open mysql db: %v", err) - } - if err := waitForDB(ctx, sqlDB, 30); err != nil { - _ = sqlDB.Close() - t.Fatalf("failed waiting for mysql db readiness: %v", err) - } - t.Cleanup(func() { _ = sqlDB.Close() }) - db := bun.NewDB(sqlDB, mysqldialect.New()) - t.Cleanup(func() { _ = db.Close() }) - - return db -} + "github.com/Authula/authula/internal/testdb" +) -func NewIntegrationTestDBFromEnv(t *testing.T) (*bun.DB, string) { +func NewIntegrationTestDB(t *testing.T, migrateFns ...func(bun.IDB)) *bun.DB { t.Helper() - - provider := strings.ToLower(strings.TrimSpace(os.Getenv("AUTHULA_TEST_DB"))) - if provider == "" { - provider = "sqlite" - } - - switch provider { - case "sqlite": - return NewSQLiteIntegrationDB(t), "sqlite" - case "postgres": - return NewPostgresIntegrationDB(t), "postgres" - case "mysql": - return NewMySQLIntegrationDB(t), "mysql" - default: - t.Fatalf("unsupported AUTHULA_TEST_DB provider %q (expected sqlite|postgres|mysql)", provider) - return nil, "" - } -} - -func openSQLiteForTests(dsn string) (*sql.DB, error) { - if sqlite3DB, err := sql.Open("sqlite3", dsn); err == nil { - if pingErr := sqlite3DB.PingContext(context.Background()); pingErr == nil { - return sqlite3DB, nil - } - _ = sqlite3DB.Close() - } - - sqliteDB, err := sql.Open("sqlite", dsn) - if err != nil { - return nil, err - } - if err := sqliteDB.PingContext(context.Background()); err != nil { - _ = sqliteDB.Close() - return nil, err - } - - return sqliteDB, nil -} - -func waitForDB(ctx context.Context, db *sql.DB, attempts int) error { - var lastErr error - for range attempts { - if err := db.PingContext(ctx); err == nil { - return nil - } else { - lastErr = err - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(300 * time.Millisecond): - } - } - return lastErr -} - -func runPostgresContainer(ctx context.Context) (container *tcpostgres.PostgresContainer, err error) { - defer func() { - if recovered := recover(); recovered != nil { - err = fmt.Errorf("panic while starting postgres testcontainer: %v", recovered) - } - }() - - return tcpostgres.Run( - ctx, - "postgres:18-alpine", - tcpostgres.WithDatabase("authula"), - tcpostgres.WithUsername("authula"), - tcpostgres.WithPassword("authula"), - ) -} - -func runMySQLContainer(ctx context.Context) (container *tcmysql.MySQLContainer, err error) { - defer func() { - if recovered := recover(); recovered != nil { - err = fmt.Errorf("panic while starting mysql testcontainer: %v", recovered) - } - }() - - return tcmysql.Run( - ctx, - "mysql:8.4", - tcmysql.WithDatabase("authula"), - tcmysql.WithUsername("authula"), - tcmysql.WithPassword("authula"), - ) -} - -func isDockerUnavailableError(err error) bool { - if err == nil { - return false - } - - msg := strings.ToLower(err.Error()) - return strings.Contains(msg, "rootless docker not found") || - strings.Contains(msg, "cannot connect to the docker daemon") || - strings.Contains(msg, "docker host") || - strings.Contains(msg, "docker socket") || - strings.Contains(msg, "permission denied") + return testdb.NewIntegrationTestDB(t, migrateFns...) } diff --git a/migrations/core.go b/migrations/core.go index dbbb22de..ec8983b0 100644 --- a/migrations/core.go +++ b/migrations/core.go @@ -2,134 +2,19 @@ package migrations import ( "context" - "fmt" "github.com/uptrace/bun" ) -// CorePluginID identifies the core migrations set. const CorePluginID = "core" -// CoreMigrationSet returns the programmatic migrations for the core schema. -func CoreMigrationSet(provider string) (MigrationSet, error) { - switch provider { - case "sqlite": - return MigrationSet{ - PluginID: CorePluginID, - Migrations: []Migration{ - coreSQLiteInitial(), - coreSQLiteAddMetadata(), - }, - }, nil - case "postgres": - return MigrationSet{ - PluginID: CorePluginID, - Migrations: []Migration{ - corePostgresInitial(), - corePostgresAddMetadata(), - }, - }, nil - case "mysql": - return MigrationSet{ - PluginID: CorePluginID, - Migrations: []Migration{ - coreMySQLInitial(), - coreMySQLAddMetadata(), - }, - }, nil - default: - return MigrationSet{}, fmt.Errorf("unsupported database provider: %s", provider) - } -} - -func coreSQLiteInitial() Migration { - return Migration{ - Version: "20260126000000_core_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return ExecStatements( - ctx, - tx, - `PRAGMA foreign_keys = ON;`, - `CREATE TABLE IF NOT EXISTS users ( - id VARCHAR(255) PRIMARY KEY, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) UNIQUE NOT NULL, - email_verified BOOLEAN DEFAULT 0, - image TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -);`, - `CREATE TABLE IF NOT EXISTS accounts ( - id VARCHAR(255) PRIMARY KEY, - user_id VARCHAR(255) NOT NULL, - account_id VARCHAR(255) NOT NULL, - provider_id VARCHAR(255) NOT NULL, - access_token TEXT, - refresh_token TEXT, - id_token TEXT, - access_token_expires_at TIMESTAMP, - refresh_token_expires_at TIMESTAMP, - scope TEXT, - password TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - UNIQUE(provider_id, account_id) -);`, - `CREATE INDEX IF NOT EXISTS idx_accounts_user_id ON accounts(user_id);`, - `CREATE TABLE IF NOT EXISTS sessions ( - id VARCHAR(255) PRIMARY KEY, - user_id VARCHAR(255) NOT NULL, - expires_at TIMESTAMP NOT NULL, - token VARCHAR(255) UNIQUE NOT NULL, - ip_address VARCHAR(45), - user_agent TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -);`, - `CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);`, - `CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);`, - `CREATE TABLE IF NOT EXISTS verifications ( - id VARCHAR(255) PRIMARY KEY, - user_id VARCHAR(255), - identifier VARCHAR(255) NOT NULL, - token VARCHAR(255) UNIQUE NOT NULL, - type VARCHAR(50) NOT NULL, - expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -);`, - `CREATE INDEX IF NOT EXISTS idx_verifications_user_id ON verifications(user_id);`, - `CREATE INDEX IF NOT EXISTS idx_verifications_identifier ON verifications(identifier);`, - `CREATE INDEX IF NOT EXISTS idx_verifications_type ON verifications(type);`, - `CREATE INDEX IF NOT EXISTS idx_verifications_expires_at ON verifications(expires_at);`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS verifications;`, - `DROP TABLE IF EXISTS sessions;`, - `DROP TABLE IF EXISTS accounts;`, - `DROP TABLE IF EXISTS users;`, - ) - }, - } -} - -func coreSQLiteAddMetadata() Migration { - return Migration{ - Version: "20260127000000_core_add_metadata", - Up: func(ctx context.Context, tx bun.Tx) error { - return ExecStatements(ctx, tx, `ALTER TABLE users ADD COLUMN metadata JSON NOT NULL DEFAULT '{}';`) +func CoreMigrationSet() (MigrationSet, error) { + return MigrationSet{ + PluginID: CorePluginID, + Migrations: []Migration{ + corePostgresInitial(), }, - Down: func(ctx context.Context, tx bun.Tx) error { - return ExecStatements(ctx, tx, `ALTER TABLE users DROP COLUMN metadata;`) - }, - } + }, nil } func corePostgresInitial() Migration { @@ -139,94 +24,94 @@ func corePostgresInitial() Migration { return ExecStatements( ctx, tx, - `CREATE EXTENSION IF NOT EXISTS pgcrypto;`, `CREATE OR REPLACE FUNCTION core_set_updated_at_fn() RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ LANGUAGE plpgsql;`, + BEGIN + NEW.updated_at = NOW(); + RETURN NEW; + END; + $$ LANGUAGE plpgsql;`, `CREATE TABLE IF NOT EXISTS users ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(255) NOT NULL, - email VARCHAR(255) UNIQUE NOT NULL, - email_verified BOOLEAN DEFAULT FALSE, - image TEXT, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - updated_at TIMESTAMP NOT NULL DEFAULT NOW() -);`, + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(255) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + email_verified BOOLEAN DEFAULT FALSE, + image TEXT, + metadata JSONB NOT NULL DEFAULT '{}'::JSONB, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() + );`, `DROP TRIGGER IF EXISTS update_users_updated_at_trigger ON users;`, `CREATE TRIGGER update_users_updated_at_trigger - BEFORE UPDATE ON users - FOR EACH ROW - EXECUTE FUNCTION core_set_updated_at_fn();`, + BEFORE UPDATE ON users + FOR EACH ROW + EXECUTE FUNCTION core_set_updated_at_fn();`, `CREATE TABLE IF NOT EXISTS accounts ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL, - account_id VARCHAR(255) NOT NULL, - provider_id VARCHAR(255) NOT NULL, - access_token TEXT, - refresh_token TEXT, - id_token TEXT, - access_token_expires_at TIMESTAMP, - refresh_token_expires_at TIMESTAMP, - scope TEXT, - password TEXT, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - updated_at TIMESTAMP NOT NULL DEFAULT NOW(), - CONSTRAINT fk_accounts_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT unique_provider_account UNIQUE(account_id, provider_id) -);`, + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL, + account_id VARCHAR(255) NOT NULL, + provider_id VARCHAR(255) NOT NULL, + access_token TEXT, + refresh_token TEXT, + id_token TEXT, + access_token_expires_at TIMESTAMP, + refresh_token_expires_at TIMESTAMP, + scope TEXT, + password TEXT, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW(), + CONSTRAINT fk_accounts_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + CONSTRAINT unique_provider_account UNIQUE(account_id, provider_id) + );`, `CREATE INDEX IF NOT EXISTS idx_accounts_user_id ON accounts(user_id);`, `DROP TRIGGER IF EXISTS update_accounts_updated_at_trigger ON accounts;`, `CREATE TRIGGER update_accounts_updated_at_trigger - BEFORE UPDATE ON accounts - FOR EACH ROW - EXECUTE FUNCTION core_set_updated_at_fn();`, + BEFORE UPDATE ON accounts + FOR EACH ROW + EXECUTE FUNCTION core_set_updated_at_fn();`, `CREATE TABLE IF NOT EXISTS sessions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL, - expires_at TIMESTAMP NOT NULL, - token VARCHAR(255) UNIQUE NOT NULL, - ip_address VARCHAR(45), - user_agent TEXT, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - updated_at TIMESTAMP NOT NULL DEFAULT NOW(), - CONSTRAINT fk_sessions_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -);`, + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL, + expires_at TIMESTAMP NOT NULL, + token VARCHAR(255) UNIQUE NOT NULL, + ip_address VARCHAR(45), + user_agent TEXT, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW(), + CONSTRAINT fk_sessions_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + );`, `CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);`, `CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);`, `DROP TRIGGER IF EXISTS update_sessions_updated_at_trigger ON sessions;`, `CREATE TRIGGER update_sessions_updated_at_trigger - BEFORE UPDATE ON sessions - FOR EACH ROW - EXECUTE FUNCTION core_set_updated_at_fn();`, + BEFORE UPDATE ON sessions + FOR EACH ROW + EXECUTE FUNCTION core_set_updated_at_fn();`, `CREATE TABLE IF NOT EXISTS verifications ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID, - identifier VARCHAR(255) NOT NULL, - token VARCHAR(255) UNIQUE NOT NULL, - type VARCHAR(50) NOT NULL, - expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - updated_at TIMESTAMP NOT NULL DEFAULT NOW(), - CONSTRAINT fk_verifications_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -);`, + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID, + identifier VARCHAR(255) NOT NULL, + token VARCHAR(255) UNIQUE NOT NULL, + type VARCHAR(50) NOT NULL, + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW(), + CONSTRAINT fk_verifications_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + );`, `CREATE INDEX IF NOT EXISTS idx_verifications_user_id ON verifications(user_id);`, `CREATE INDEX IF NOT EXISTS idx_verifications_identifier ON verifications(identifier);`, `CREATE INDEX IF NOT EXISTS idx_verifications_type ON verifications(type);`, `CREATE INDEX IF NOT EXISTS idx_verifications_expires_at ON verifications(expires_at);`, `DROP TRIGGER IF EXISTS update_verifications_updated_at_trigger ON verifications;`, `CREATE TRIGGER update_verifications_updated_at_trigger - BEFORE UPDATE ON verifications - FOR EACH ROW - EXECUTE FUNCTION core_set_updated_at_fn();`, + BEFORE UPDATE ON verifications + FOR EACH ROW + EXECUTE FUNCTION core_set_updated_at_fn();`, `CREATE OR REPLACE FUNCTION cleanup_expired_records_fn() RETURNS void AS $$ -BEGIN - DELETE FROM sessions WHERE expires_at < NOW(); - DELETE FROM verifications WHERE expires_at < NOW(); -END; -$$ LANGUAGE plpgsql;`, + BEGIN + DELETE FROM sessions WHERE expires_at < NOW(); + DELETE FROM verifications WHERE expires_at < NOW(); + END; + $$ LANGUAGE plpgsql;`, ) }, Down: func(ctx context.Context, tx bun.Tx) error { @@ -247,125 +132,3 @@ $$ LANGUAGE plpgsql;`, }, } } - -func corePostgresAddMetadata() Migration { - return Migration{ - Version: "20260127000000_core_add_metadata", - Up: func(ctx context.Context, tx bun.Tx) error { - return ExecStatements(ctx, tx, `ALTER TABLE users ADD COLUMN metadata JSONB NOT NULL DEFAULT '{}'::JSONB;`) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return ExecStatements(ctx, tx, `ALTER TABLE users DROP COLUMN metadata;`) - }, - } -} - -func coreMySQLInitial() Migration { - return Migration{ - Version: "20260126000000_core_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS users ( - id BINARY(16) NOT NULL PRIMARY KEY, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) UNIQUE NOT NULL, - email_verified TINYINT(1) DEFAULT 0, - image LONGTEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - `CREATE TABLE IF NOT EXISTS accounts ( - id BINARY(16) NOT NULL PRIMARY KEY, - user_id BINARY(16) NOT NULL, - account_id VARCHAR(255) NOT NULL, - provider_id VARCHAR(255) NOT NULL, - access_token LONGTEXT, - refresh_token LONGTEXT, - id_token LONGTEXT, - access_token_expires_at TIMESTAMP NULL, - refresh_token_expires_at TIMESTAMP NULL, - scope TEXT, - password TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_accounts_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT unique_provider_account UNIQUE(account_id, provider_id), - INDEX idx_accounts_user_id (user_id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - `CREATE TABLE IF NOT EXISTS sessions ( - id BINARY(16) NOT NULL PRIMARY KEY, - user_id BINARY(16) NOT NULL, - expires_at TIMESTAMP NOT NULL, - token VARCHAR(255) UNIQUE NOT NULL, - ip_address VARCHAR(45), - user_agent TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_sessions_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - UNIQUE INDEX idx_sessions_token (token), - INDEX idx_sessions_user_id (user_id), - INDEX idx_sessions_expires_at (expires_at) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - `CREATE TABLE IF NOT EXISTS verifications ( - id BINARY(16) NOT NULL PRIMARY KEY, - user_id BINARY(16) NULL, - identifier VARCHAR(255) NOT NULL, - token VARCHAR(255) UNIQUE NOT NULL, - type VARCHAR(50) NOT NULL, - expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_verifications_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - INDEX idx_verifications_user_id (user_id), - INDEX idx_verifications_identifier (identifier), - INDEX idx_verifications_token (token), - INDEX idx_verifications_type (type), - INDEX idx_verifications_expires_at (expires_at) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - `DROP PROCEDURE IF EXISTS cleanup_sessions;`, - `DROP PROCEDURE IF EXISTS cleanup_verifications;`, - `DROP PROCEDURE IF EXISTS cleanup_all_expired;`, - `CREATE PROCEDURE cleanup_sessions() -BEGIN - DELETE FROM sessions WHERE expires_at < NOW(); -END;`, - `CREATE PROCEDURE cleanup_verifications() -BEGIN - DELETE FROM verifications WHERE expires_at < NOW(); -END;`, - `CREATE PROCEDURE cleanup_all_expired() -BEGIN - CALL cleanup_sessions(); - CALL cleanup_verifications(); -END;`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return ExecStatements( - ctx, - tx, - `DROP PROCEDURE IF EXISTS cleanup_all_expired;`, - `DROP PROCEDURE IF EXISTS cleanup_verifications;`, - `DROP PROCEDURE IF EXISTS cleanup_sessions;`, - `DROP TABLE IF EXISTS verifications;`, - `DROP TABLE IF EXISTS sessions;`, - `DROP TABLE IF EXISTS accounts;`, - `DROP TABLE IF EXISTS users;`, - ) - }, - } -} - -func coreMySQLAddMetadata() Migration { - return Migration{ - Version: "20260127000000_core_add_metadata", - Up: func(ctx context.Context, tx bun.Tx) error { - return ExecStatements(ctx, tx, `ALTER TABLE users ADD COLUMN metadata JSON;`) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return ExecStatements(ctx, tx, `ALTER TABLE users DROP COLUMN metadata;`) - }, - } -} diff --git a/migrations/migrator_test.go b/migrations/migrator_test.go index 582bf289..1af45116 100644 --- a/migrations/migrator_test.go +++ b/migrations/migrator_test.go @@ -2,16 +2,12 @@ package migrations import ( "context" - "database/sql" - "fmt" - "strings" "testing" "github.com/stretchr/testify/assert" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/sqlitedialect" - _ "github.com/mattn/go-sqlite3" + "github.com/Authula/authula/internal/testdb" ) type testLogger struct { @@ -26,19 +22,7 @@ func (l testLogger) Error(msg string, args ...any) {} func newTestDB(t *testing.T) *bun.DB { t.Helper() - dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", strings.ReplaceAll(t.Name(), "/", "_")) - sqlDB, err := sql.Open("sqlite3", dsn) - assert.NoError(t, err) - t.Cleanup(func() { - _ = sqlDB.Close() - }) - - db := bun.NewDB(sqlDB, sqlitedialect.New()) - t.Cleanup(func() { - _ = db.Close() - }) - - return db + return testdb.NewIntegrationTestDB(t) } func TestMigrator_MigrateAndRollback(t *testing.T) { diff --git a/migrations/provider_variants.go b/migrations/provider_variants.go deleted file mode 100644 index 93dda5a0..00000000 --- a/migrations/provider_variants.go +++ /dev/null @@ -1,24 +0,0 @@ -package migrations - -import "strings" - -// ProviderVariants maps a database provider to a lazy migration constructor. -type ProviderVariants map[string]func() []Migration - -// ForProvider returns the migrations for the requested provider using the supplied variants. -func ForProvider(provider string, variants ProviderVariants) []Migration { - if len(variants) == 0 { - return nil - } - - key := strings.ToLower(strings.TrimSpace(provider)) - if key == "" { - return nil - } - - if constructor, ok := variants[key]; ok && constructor != nil { - return constructor() - } - - return nil -} diff --git a/models/config.go b/models/config.go index 1c826219..11b83fa1 100644 --- a/models/config.go +++ b/models/config.go @@ -35,7 +35,6 @@ type Config struct { } type DatabaseConfig struct { - Provider string `json:"provider" toml:"provider"` URL string `json:"url" toml:"url"` MaxOpenConns int `json:"max_open_conns" toml:"max_open_conns"` MaxIdleConns int `json:"max_idle_conns" toml:"max_idle_conns"` @@ -86,7 +85,6 @@ type EventBusConfig struct { ContextTimeout time.Duration `json:"context_timeout" toml:"context_timeout"` Provider events.EventBusProvider `json:"provider" toml:"provider"` GoChannel *GoChannelConfig `json:"go_channel" toml:"go_channel"` - SQLite *SQLiteConfig `json:"sqlite" toml:"sqlite"` PostgreSQL *PostgreSQLConfig `json:"postgres" toml:"postgres"` Redis *RedisConfig `json:"redis" toml:"redis"` Kafka *KafkaConfig `json:"kafka" toml:"kafka"` @@ -98,10 +96,6 @@ type GoChannelConfig struct { BufferSize int `json:"buffer_size" toml:"buffer_size"` } -type SQLiteConfig struct { - DBPath string `json:"db_path" toml:"db_path"` -} - type PostgreSQLConfig struct { URL string `json:"url" toml:"url"` } diff --git a/models/plugin.go b/models/plugin.go index cd268ac0..0982f183 100644 --- a/models/plugin.go +++ b/models/plugin.go @@ -59,7 +59,7 @@ type Plugin interface { // PluginWithMigrations is an optional interface for plugins that have database migrations type PluginWithMigrations interface { - Migrations(provider string) []migrations.Migration + Migrations() []migrations.Migration DependsOn() []string } diff --git a/plugins/access-control/migrations.go b/plugins/access-control/migrations.go index d2faacc0..0559fbaf 100644 --- a/plugins/access-control/migrations.go +++ b/plugins/access-control/migrations.go @@ -5,6 +5,6 @@ import ( "github.com/Authula/authula/plugins/access-control/migrationset" ) -func accessControlMigrationsForProvider(provider string) []migrations.Migration { - return migrationset.ForProvider(provider) +func accessControlMigrations() []migrations.Migration { + return migrationset.Migrations() } diff --git a/plugins/access-control/migrationset/migrations.go b/plugins/access-control/migrationset/migrations.go index 1784586c..d5637707 100644 --- a/plugins/access-control/migrationset/migrations.go +++ b/plugins/access-control/migrationset/migrations.go @@ -8,97 +8,11 @@ import ( "github.com/Authula/authula/migrations" ) -func ForProvider(provider string) []migrations.Migration { - return migrations.ForProvider(provider, migrations.ProviderVariants{ - "sqlite": func() []migrations.Migration { return []migrations.Migration{accessControlSQLiteInitial()} }, - "postgres": func() []migrations.Migration { return []migrations.Migration{accessControlPostgresInitial()} }, - "mysql": func() []migrations.Migration { return []migrations.Migration{accessControlMySQLInitial()} }, - }) +func Migrations() []migrations.Migration { + return []migrations.Migration{postgresInitial()} } -func accessControlSQLiteInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260309000000_access_control_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `PRAGMA foreign_keys = ON;`, - // ------------------------------- - `CREATE TABLE IF NOT EXISTS access_control_roles ( - id TEXT PRIMARY KEY, - name VARCHAR(255) NOT NULL UNIQUE, - description TEXT, - weight INTEGER NOT NULL, - is_system BOOLEAN NOT NULL DEFAULT 0, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP - );`, - `DROP TRIGGER IF EXISTS update_roles_updated_at_trigger;`, - `CREATE TRIGGER update_roles_updated_at_trigger - AFTER UPDATE ON access_control_roles - BEGIN - UPDATE access_control_roles SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; - END;`, - // ------------------------------- - `CREATE TABLE IF NOT EXISTS access_control_permissions ( - id TEXT PRIMARY KEY, - key VARCHAR(255) NOT NULL UNIQUE, - description TEXT, - is_system BOOLEAN NOT NULL DEFAULT 0, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP - );`, - `DROP TRIGGER IF EXISTS update_permissions_updated_at_trigger;`, - `CREATE TRIGGER update_permissions_updated_at_trigger - AFTER UPDATE ON access_control_permissions - BEGIN - UPDATE access_control_permissions SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; - END;`, - // ------------------------------- - `CREATE TABLE IF NOT EXISTS access_control_role_permissions ( - role_id TEXT NOT NULL, - permission_id TEXT NOT NULL, - granted_by_user_id TEXT, - granted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (role_id, permission_id), - FOREIGN KEY (role_id) REFERENCES access_control_roles(id) ON DELETE CASCADE, - FOREIGN KEY (permission_id) REFERENCES access_control_permissions(id) ON DELETE CASCADE, - FOREIGN KEY (granted_by_user_id) REFERENCES users(id) ON DELETE SET NULL - );`, - `CREATE INDEX IF NOT EXISTS idx_access_control_role_permissions_role_id ON access_control_role_permissions(role_id);`, - `CREATE INDEX IF NOT EXISTS idx_access_control_role_permissions_permission_id ON access_control_role_permissions(permission_id);`, - // ------------------------------- - `CREATE TABLE IF NOT EXISTS access_control_user_roles ( - user_id TEXT NOT NULL, - role_id TEXT NOT NULL, - assigned_by_user_id TEXT, - assigned_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP, - PRIMARY KEY (user_id, role_id), - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - FOREIGN KEY (role_id) REFERENCES access_control_roles(id) ON DELETE CASCADE, - FOREIGN KEY (assigned_by_user_id) REFERENCES users(id) ON DELETE SET NULL - );`, - `CREATE INDEX IF NOT EXISTS idx_access_control_user_roles_role_id ON access_control_user_roles(role_id);`, - `CREATE INDEX IF NOT EXISTS idx_access_control_user_roles_expires_at ON access_control_user_roles(expires_at);`, - // ------------------------------- - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS access_control_user_roles;`, - `DROP TABLE IF EXISTS access_control_role_permissions;`, - `DROP TABLE IF EXISTS access_control_permissions;`, - `DROP TABLE IF EXISTS access_control_roles;`, - ) - }, - } -} - -func accessControlPostgresInitial() migrations.Migration { +func postgresInitial() migrations.Migration { return migrations.Migration{ Version: "20260309000000_access_control_initial", Up: func(ctx context.Context, tx bun.Tx) error { @@ -186,71 +100,3 @@ func accessControlPostgresInitial() migrations.Migration { }, } } - -func accessControlMySQLInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260309000000_access_control_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS access_control_roles ( - id BINARY(16) NOT NULL PRIMARY KEY, - name VARCHAR(255) NOT NULL UNIQUE, - description TEXT, - weight INT NOT NULL, - is_system TINYINT(1) NOT NULL DEFAULT 0, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - // ------------------------------- - `CREATE TABLE IF NOT EXISTS access_control_permissions ( - id BINARY(16) NOT NULL PRIMARY KEY, - key VARCHAR(255) NOT NULL UNIQUE, - description TEXT, - is_system TINYINT(1) NOT NULL DEFAULT 0, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - // ------------------------------- - `CREATE TABLE IF NOT EXISTS access_control_role_permissions ( - role_id BINARY(16) NOT NULL, - permission_id BINARY(16) NOT NULL, - granted_by_user_id BINARY(16) NULL, - granted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (role_id, permission_id), - CONSTRAINT fk_access_control_role_permissions_role_id FOREIGN KEY (role_id) REFERENCES access_control_roles(id) ON DELETE CASCADE, - CONSTRAINT fk_access_control_role_permissions_permission_id FOREIGN KEY (permission_id) REFERENCES access_control_permissions(id) ON DELETE CASCADE, - CONSTRAINT fk_access_control_role_permissions_granted_by_user_id FOREIGN KEY (granted_by_user_id) REFERENCES users(id) ON DELETE SET NULL, - INDEX idx_access_control_role_permissions_role_id (role_id), - INDEX idx_access_control_role_permissions_permission_id (permission_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - // ------------------------------- - `CREATE TABLE IF NOT EXISTS access_control_user_roles ( - user_id BINARY(16) NOT NULL, - role_id BINARY(16) NOT NULL, - assigned_by_user_id BINARY(16) NULL, - assigned_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP NULL, - PRIMARY KEY (user_id, role_id), - CONSTRAINT fk_access_control_user_roles_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT fk_access_control_user_roles_role_id FOREIGN KEY (role_id) REFERENCES access_control_roles(id) ON DELETE CASCADE, - CONSTRAINT fk_access_control_user_roles_assigned_by_user_id FOREIGN KEY (assigned_by_user_id) REFERENCES users(id) ON DELETE SET NULL, - INDEX idx_access_control_user_roles_role_id (role_id), - INDEX idx_access_control_user_roles_expires_at (expires_at) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - // ------------------------------- - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS access_control_user_roles;`, - `DROP TABLE IF EXISTS access_control_role_permissions;`, - `DROP TABLE IF EXISTS access_control_permissions;`, - `DROP TABLE IF EXISTS access_control_roles;`, - ) - }, - } -} diff --git a/plugins/access-control/plugin.go b/plugins/access-control/plugin.go index 9948de16..ced869a9 100644 --- a/plugins/access-control/plugin.go +++ b/plugins/access-control/plugin.go @@ -82,8 +82,8 @@ func (p *AccessControlPlugin) Init(ctx *models.PluginContext) error { return nil } -func (p *AccessControlPlugin) Migrations(provider string) []migrations.Migration { - return accessControlMigrationsForProvider(provider) +func (p *AccessControlPlugin) Migrations() []migrations.Migration { + return accessControlMigrations() } func (p *AccessControlPlugin) DependsOn() []string { diff --git a/plugins/access-control/repositories/permissions_repository_test.go b/plugins/access-control/repositories/permissions_repository_test.go index 1d4f4f84..65cff4c7 100644 --- a/plugins/access-control/repositories/permissions_repository_test.go +++ b/plugins/access-control/repositories/permissions_repository_test.go @@ -2,9 +2,10 @@ package repositories import ( "context" - "strings" "testing" + "github.com/google/uuid" + plugintests "github.com/Authula/authula/plugins/access-control/tests" "github.com/Authula/authula/plugins/access-control/types" ) @@ -12,6 +13,9 @@ import ( func TestBunPermissionsRepositoryCreatePermission(t *testing.T) { t.Parallel() + p1 := uuid.New().String() + p2 := uuid.New().String() + tests := []struct { name string permission *types.Permission @@ -22,14 +26,14 @@ func TestBunPermissionsRepositoryCreatePermission(t *testing.T) { }{ { name: "success", - permission: &types.Permission{ID: "p1", Key: "users.read", Description: new("Read users"), IsSystem: false}, - wantID: "p1", + permission: &types.Permission{ID: p1, Key: "users.read", Description: new("Read users"), IsSystem: false}, + wantID: p1, wantKey: "users.read", wantDescription: new("Read users"), }, { name: "duplicate key returns conflict", - permission: &types.Permission{ID: "p2", Key: "users.read", Description: new("Duplicate"), IsSystem: false}, + permission: &types.Permission{ID: p2, Key: "users.read", Description: new("Duplicate"), IsSystem: false}, }, } @@ -37,12 +41,14 @@ func TestBunPermissionsRepositoryCreatePermission(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + user1ID := uuid.New().String() + user2ID := uuid.New().String() + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := NewBunPermissionsRepository(db) ctx := context.Background() if tc.name == "duplicate key returns conflict" { - if err := repo.CreatePermission(ctx, &types.Permission{ID: "p1", Key: "users.read", Description: new("Read users"), IsSystem: false}); err != nil { + if err := repo.CreatePermission(ctx, &types.Permission{ID: p1, Key: "users.read", Description: new("Read users"), IsSystem: false}); err != nil { t.Fatalf("failed to seed permission: %v", err) } } @@ -52,9 +58,6 @@ func TestBunPermissionsRepositoryCreatePermission(t *testing.T) { if err == nil { t.Fatal("expected error, got nil") } - if !strings.Contains(err.Error(), "UNIQUE constraint failed: access_control_permissions.key") { - t.Fatalf("expected raw unique constraint error, got %v", err) - } return } if err != tc.wantErr { @@ -91,14 +94,18 @@ func TestBunPermissionsRepositoryCreatePermission(t *testing.T) { func TestBunPermissionsRepositoryGetAllPermissions(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + user1ID := uuid.New().String() + user2ID := uuid.New().String() + p1 := uuid.New().String() + p2 := uuid.New().String() + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := NewBunPermissionsRepository(db) ctx := context.Background() - if err := repo.CreatePermission(ctx, &types.Permission{ID: "p2", Key: "users.write"}); err != nil { + if err := repo.CreatePermission(ctx, &types.Permission{ID: p2, Key: "users.write"}); err != nil { t.Fatalf("failed to seed permission p2: %v", err) } - if err := repo.CreatePermission(ctx, &types.Permission{ID: "p1", Key: "users.read"}); err != nil { + if err := repo.CreatePermission(ctx, &types.Permission{ID: p1, Key: "users.read"}); err != nil { t.Fatalf("failed to seed permission p1: %v", err) } @@ -109,7 +116,7 @@ func TestBunPermissionsRepositoryGetAllPermissions(t *testing.T) { if len(permissions) != 2 { t.Fatalf("expected 2 permissions, got %d", len(permissions)) } - if permissions[0].ID != "p2" || permissions[1].ID != "p1" { + if permissions[0].ID != p2 || permissions[1].ID != p1 { t.Fatalf("expected permissions ordered by creation time, got %#v", permissions) } } @@ -117,6 +124,8 @@ func TestBunPermissionsRepositoryGetAllPermissions(t *testing.T) { func TestBunPermissionsRepositoryGetPermissionByID(t *testing.T) { t.Parallel() + p1 := uuid.New().String() + tests := []struct { name string permissionID string @@ -125,13 +134,13 @@ func TestBunPermissionsRepositoryGetPermissionByID(t *testing.T) { }{ { name: "not found", - permissionID: "missing", + permissionID: "00000000-0000-0000-0000-000000000000", wantNil: true, }, { name: "success", - permissionID: "p1", - seedPermission: &types.Permission{ID: "p1", Key: "users.read", Description: new("Read users"), IsSystem: false}, + permissionID: p1, + seedPermission: &types.Permission{ID: p1, Key: "users.read", Description: new("Read users"), IsSystem: false}, }, } @@ -139,7 +148,9 @@ func TestBunPermissionsRepositoryGetPermissionByID(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + user1ID := uuid.New().String() + user2ID := uuid.New().String() + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := NewBunPermissionsRepository(db) ctx := context.Background() @@ -169,6 +180,8 @@ func TestBunPermissionsRepositoryGetPermissionByID(t *testing.T) { func TestBunPermissionsRepositoryUpdatePermission(t *testing.T) { t.Parallel() + p1 := uuid.New().String() + tests := []struct { name string seedPermission *types.Permission @@ -178,14 +191,14 @@ func TestBunPermissionsRepositoryUpdatePermission(t *testing.T) { }{ { name: "missing permission", - permissionID: "missing", + permissionID: "00000000-0000-0000-0000-000000000000", description: new("updated"), wantUpdated: false, }, { name: "success", - seedPermission: &types.Permission{ID: "p1", Key: "users.read", Description: new("Read users"), IsSystem: false}, - permissionID: "p1", + seedPermission: &types.Permission{ID: p1, Key: "users.read", Description: new("Read users"), IsSystem: false}, + permissionID: p1, description: new("updated"), wantUpdated: true, }, @@ -195,7 +208,9 @@ func TestBunPermissionsRepositoryUpdatePermission(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + user1ID := uuid.New().String() + user2ID := uuid.New().String() + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := NewBunPermissionsRepository(db) ctx := context.Background() @@ -229,6 +244,8 @@ func TestBunPermissionsRepositoryUpdatePermission(t *testing.T) { func TestBunPermissionsRepositoryDeletePermission(t *testing.T) { t.Parallel() + p1 := uuid.New().String() + tests := []struct { name string seedPermission *types.Permission @@ -237,13 +254,13 @@ func TestBunPermissionsRepositoryDeletePermission(t *testing.T) { }{ { name: "missing permission", - permissionID: "missing", + permissionID: "00000000-0000-0000-0000-000000000000", wantDeleted: false, }, { name: "success", - seedPermission: &types.Permission{ID: "p1", Key: "users.read", Description: new("Read users"), IsSystem: false}, - permissionID: "p1", + seedPermission: &types.Permission{ID: p1, Key: "users.read", Description: new("Read users"), IsSystem: false}, + permissionID: p1, wantDeleted: true, }, } @@ -252,7 +269,9 @@ func TestBunPermissionsRepositoryDeletePermission(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + user1ID := uuid.New().String() + user2ID := uuid.New().String() + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := NewBunPermissionsRepository(db) ctx := context.Background() diff --git a/plugins/access-control/repositories/role_permission_repository_test.go b/plugins/access-control/repositories/role_permission_repository_test.go index 180eed59..8fce7e40 100644 --- a/plugins/access-control/repositories/role_permission_repository_test.go +++ b/plugins/access-control/repositories/role_permission_repository_test.go @@ -2,9 +2,10 @@ package repositories import ( "context" - "strings" "testing" + "github.com/google/uuid" + plugintests "github.com/Authula/authula/plugins/access-control/tests" "github.com/Authula/authula/plugins/access-control/types" ) @@ -12,6 +13,12 @@ import ( func TestBunRolePermissionsRepositoryGetRolePermissions(t *testing.T) { t.Parallel() + roleID := uuid.New().String() + perm1ID := uuid.New().String() + perm2ID := uuid.New().String() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + tests := []struct { name string seed func(*BunRolesRepository, *BunPermissionsRepository, *BunRolePermissionsRepository, context.Context) @@ -22,33 +29,33 @@ func TestBunRolePermissionsRepositoryGetRolePermissions(t *testing.T) { }{ { name: "empty result", - roleID: "role-missing", + roleID: "00000000-0000-0000-0000-000000000000", wantNil: false, wantIDs: []string{}, wantKeys: []string{}, }, { name: "success", - roleID: "role-1", + roleID: roleID, seed: func(rolesRepo *BunRolesRepository, permissionsRepo *BunPermissionsRepository, rolePermissionsRepo *BunRolePermissionsRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "role-1", Name: "editor", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: roleID, Name: "editor", Weight: 10}); err != nil { panic(err) } - if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: "perm-2", Key: "users.write", Description: new("Write users")}); err != nil { + if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: perm2ID, Key: "users.write", Description: new("Write users")}); err != nil { panic(err) } - if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: "perm-1", Key: "users.read", Description: new("Read users")}); err != nil { + if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: perm1ID, Key: "users.read", Description: new("Read users")}); err != nil { panic(err) } - grantedBy := "u2" - if err := rolePermissionsRepo.AddRolePermission(ctx, "role-1", "perm-2", &grantedBy); err != nil { + grantedBy := user2ID + if err := rolePermissionsRepo.AddRolePermission(ctx, roleID, perm2ID, &grantedBy); err != nil { panic(err) } - if err := rolePermissionsRepo.AddRolePermission(ctx, "role-1", "perm-1", &grantedBy); err != nil { + if err := rolePermissionsRepo.AddRolePermission(ctx, roleID, perm1ID, &grantedBy); err != nil { panic(err) } }, - wantIDs: []string{"perm-1", "perm-2"}, + wantIDs: []string{perm1ID, perm2ID}, wantKeys: []string{"users.read", "users.write"}, }, } @@ -57,7 +64,7 @@ func TestBunRolePermissionsRepositoryGetRolePermissions(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) rolesRepo := NewBunRolesRepository(db) permissionsRepo := NewBunPermissionsRepository(db) rolePermissionsRepo := NewBunRolePermissionsRepository(db) @@ -89,6 +96,11 @@ func TestBunRolePermissionsRepositoryGetRolePermissions(t *testing.T) { func TestBunRolePermissionsRepositoryAddRolePermission(t *testing.T) { t.Parallel() + roleID := uuid.New().String() + perm1ID := uuid.New().String() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + tests := []struct { name string setup func(*BunRolesRepository, *BunPermissionsRepository, *BunRolePermissionsRepository, context.Context) @@ -99,29 +111,29 @@ func TestBunRolePermissionsRepositoryAddRolePermission(t *testing.T) { }{ { name: "success", - roleID: "role-1", - permissionID: "perm-1", + roleID: roleID, + permissionID: perm1ID, setup: func(rolesRepo *BunRolesRepository, permissionsRepo *BunPermissionsRepository, rolePermissionsRepo *BunRolePermissionsRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "role-1", Name: "editor", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: roleID, Name: "editor", Weight: 10}); err != nil { panic(err) } - if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: "perm-1", Key: "users.read"}); err != nil { + if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: perm1ID, Key: "users.read"}); err != nil { panic(err) } }, }, { name: "duplicate grant returns conflict", - roleID: "role-1", - permissionID: "perm-1", + roleID: roleID, + permissionID: perm1ID, setup: func(rolesRepo *BunRolesRepository, permissionsRepo *BunPermissionsRepository, rolePermissionsRepo *BunRolePermissionsRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "role-1", Name: "editor", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: roleID, Name: "editor", Weight: 10}); err != nil { panic(err) } - if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: "perm-1", Key: "users.read"}); err != nil { + if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: perm1ID, Key: "users.read"}); err != nil { panic(err) } - if err := rolePermissionsRepo.AddRolePermission(ctx, "role-1", "perm-1", nil); err != nil { + if err := rolePermissionsRepo.AddRolePermission(ctx, roleID, perm1ID, nil); err != nil { panic(err) } }, @@ -132,7 +144,7 @@ func TestBunRolePermissionsRepositoryAddRolePermission(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) rolesRepo := NewBunRolesRepository(db) permissionsRepo := NewBunPermissionsRepository(db) rolePermissionsRepo := NewBunRolePermissionsRepository(db) @@ -147,9 +159,6 @@ func TestBunRolePermissionsRepositoryAddRolePermission(t *testing.T) { if err == nil { t.Fatal("expected error, got nil") } - if !strings.Contains(err.Error(), "UNIQUE constraint failed: access_control_role_permissions.role_id, access_control_role_permissions.permission_id") { - t.Fatalf("expected raw unique constraint error, got %v", err) - } return } if err != tc.wantErr { @@ -177,6 +186,12 @@ func TestBunRolePermissionsRepositoryAddRolePermission(t *testing.T) { func TestBunRolePermissionsRepositoryReplaceRolePermissions(t *testing.T) { t.Parallel() + roleID := uuid.New().String() + perm1ID := uuid.New().String() + perm2ID := uuid.New().String() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + tests := []struct { name string seed func(*BunRolesRepository, *BunPermissionsRepository, *BunRolePermissionsRepository, context.Context) @@ -186,23 +201,23 @@ func TestBunRolePermissionsRepositoryReplaceRolePermissions(t *testing.T) { }{ { name: "success", - roleID: "role-1", - permissionIDs: []string{"perm-2", "perm-1"}, + roleID: roleID, + permissionIDs: []string{perm2ID, perm1ID}, seed: func(rolesRepo *BunRolesRepository, permissionsRepo *BunPermissionsRepository, rolePermissionsRepo *BunRolePermissionsRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "role-1", Name: "editor", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: roleID, Name: "editor", Weight: 10}); err != nil { panic(err) } - if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: "perm-1", Key: "users.read"}); err != nil { + if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: perm1ID, Key: "users.read"}); err != nil { panic(err) } - if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: "perm-2", Key: "users.write"}); err != nil { + if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: perm2ID, Key: "users.write"}); err != nil { panic(err) } - if err := rolePermissionsRepo.AddRolePermission(ctx, "role-1", "perm-2", nil); err != nil { + if err := rolePermissionsRepo.AddRolePermission(ctx, roleID, perm2ID, nil); err != nil { panic(err) } }, - wantIDs: []string{"perm-1", "perm-2"}, + wantIDs: []string{perm1ID, perm2ID}, }, } @@ -210,7 +225,7 @@ func TestBunRolePermissionsRepositoryReplaceRolePermissions(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) rolesRepo := NewBunRolesRepository(db) permissionsRepo := NewBunPermissionsRepository(db) rolePermissionsRepo := NewBunRolePermissionsRepository(db) @@ -243,6 +258,11 @@ func TestBunRolePermissionsRepositoryReplaceRolePermissions(t *testing.T) { func TestBunRolePermissionsRepositoryRemoveRolePermission(t *testing.T) { t.Parallel() + roleID := uuid.New().String() + perm1ID := uuid.New().String() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + tests := []struct { name string seed func(*BunRolesRepository, *BunPermissionsRepository, *BunRolePermissionsRepository, context.Context) @@ -252,16 +272,16 @@ func TestBunRolePermissionsRepositoryRemoveRolePermission(t *testing.T) { }{ { name: "success", - roleID: "role-1", - permissionID: "perm-1", + roleID: roleID, + permissionID: perm1ID, seed: func(rolesRepo *BunRolesRepository, permissionsRepo *BunPermissionsRepository, rolePermissionsRepo *BunRolePermissionsRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "role-1", Name: "editor", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: roleID, Name: "editor", Weight: 10}); err != nil { panic(err) } - if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: "perm-1", Key: "users.read"}); err != nil { + if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: perm1ID, Key: "users.read"}); err != nil { panic(err) } - if err := rolePermissionsRepo.AddRolePermission(ctx, "role-1", "perm-1", nil); err != nil { + if err := rolePermissionsRepo.AddRolePermission(ctx, roleID, perm1ID, nil); err != nil { panic(err) } }, @@ -273,7 +293,7 @@ func TestBunRolePermissionsRepositoryRemoveRolePermission(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) rolesRepo := NewBunRolesRepository(db) permissionsRepo := NewBunPermissionsRepository(db) rolePermissionsRepo := NewBunRolePermissionsRepository(db) diff --git a/plugins/access-control/repositories/roles_repository_test.go b/plugins/access-control/repositories/roles_repository_test.go index 7e6c937d..6566f494 100644 --- a/plugins/access-control/repositories/roles_repository_test.go +++ b/plugins/access-control/repositories/roles_repository_test.go @@ -5,6 +5,8 @@ import ( "strings" "testing" + "github.com/google/uuid" + plugintests "github.com/Authula/authula/plugins/access-control/tests" "github.com/Authula/authula/plugins/access-control/types" ) @@ -12,6 +14,10 @@ import ( func TestBunRolesRepositoryCreateRole(t *testing.T) { t.Parallel() + r1 := uuid.New().String() + r2 := uuid.New().String() + r3 := uuid.New().String() + tests := []struct { name string role *types.Role @@ -24,21 +30,21 @@ func TestBunRolesRepositoryCreateRole(t *testing.T) { }{ { name: "success", - role: &types.Role{ID: "r1", Name: "editor", Description: new("Editor role"), Weight: 10, IsSystem: false}, - wantID: "r1", + role: &types.Role{ID: r1, Name: "editor", Description: new("Editor role"), Weight: 10, IsSystem: false}, + wantID: r1, wantName: "editor", wantDesc: new("Editor role"), wantWeight: 10, }, { name: "duplicate name returns conflict", - role: &types.Role{ID: "r2", Name: "editor", Description: new("Duplicate role"), Weight: 10, IsSystem: false}, - seed: &types.Role{ID: "r1", Name: "editor", Description: new("Original role"), Weight: 10, IsSystem: false}, + role: &types.Role{ID: r2, Name: "editor", Description: new("Duplicate role"), Weight: 10, IsSystem: false}, + seed: &types.Role{ID: r1, Name: "editor", Description: new("Original role"), Weight: 10, IsSystem: false}, wantErr: nil, }, { name: "query error returns wrapped error", - role: &types.Role{ID: "r3", Name: "reviewer", Description: new("Reviewer role"), Weight: 10, IsSystem: false}, + role: &types.Role{ID: r3, Name: "reviewer", Description: new("Reviewer role"), Weight: 10, IsSystem: false}, wantErr: nil, }, } @@ -47,7 +53,9 @@ func TestBunRolesRepositoryCreateRole(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + user1ID := uuid.New().String() + user2ID := uuid.New().String() + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := NewBunRolesRepository(db) ctx := context.Background() @@ -77,9 +85,6 @@ func TestBunRolesRepositoryCreateRole(t *testing.T) { if err == nil { t.Fatal("expected error, got nil") } - if !strings.Contains(err.Error(), "UNIQUE constraint failed: access_control_roles.name") { - t.Fatalf("expected raw unique constraint error, got %v", err) - } return } if err != tc.wantErr { @@ -117,6 +122,9 @@ func TestBunRolesRepositoryCreateRole(t *testing.T) { func TestBunRolesRepositoryGetAllRoles(t *testing.T) { t.Parallel() + r1 := uuid.New().String() + r2 := uuid.New().String() + tests := []struct { name string seedRoles []*types.Role @@ -136,10 +144,10 @@ func TestBunRolesRepositoryGetAllRoles(t *testing.T) { { name: "returns roles ordered by weight", seedRoles: []*types.Role{ - {ID: "r2", Name: "viewer", Description: new("Viewer role"), Weight: 10}, - {ID: "r1", Name: "editor", Description: new("Editor role"), Weight: 30}, + {ID: r2, Name: "viewer", Description: new("Viewer role"), Weight: 10}, + {ID: r1, Name: "editor", Description: new("Editor role"), Weight: 30}, }, - wantIDs: []string{"r1", "r2"}, + wantIDs: []string{r1, r2}, wantNames: []string{"editor", "viewer"}, wantDescs: []*string{new("Editor role"), new("Viewer role")}, wantWeights: []int{30, 10}, @@ -154,7 +162,9 @@ func TestBunRolesRepositoryGetAllRoles(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + user1ID := uuid.New().String() + user2ID := uuid.New().String() + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := NewBunRolesRepository(db) ctx := context.Background() @@ -213,6 +223,8 @@ func TestBunRolesRepositoryGetAllRoles(t *testing.T) { func TestBunRolesRepositoryGetRoleByID(t *testing.T) { t.Parallel() + r1 := uuid.New().String() + tests := []struct { name string roleID string @@ -226,13 +238,13 @@ func TestBunRolesRepositoryGetRoleByID(t *testing.T) { }{ { name: "not found", - roleID: "missing", + roleID: "00000000-0000-0000-0000-000000000000", wantNil: true, }, { name: "success", - roleID: "r1", - seedRole: &types.Role{ID: "r1", Name: "editor", Description: new("Editor role"), IsSystem: true, Weight: 20}, + roleID: r1, + seedRole: &types.Role{ID: r1, Name: "editor", Description: new("Editor role"), IsSystem: true, Weight: 20}, wantName: "editor", wantDesc: new("Editor role"), wantSystem: true, @@ -240,7 +252,7 @@ func TestBunRolesRepositoryGetRoleByID(t *testing.T) { }, { name: "query error", - roleID: "r1", + roleID: r1, wantErrMsg: "failed to get role by id", }, } @@ -249,7 +261,9 @@ func TestBunRolesRepositoryGetRoleByID(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + user1ID := uuid.New().String() + user2ID := uuid.New().String() + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := NewBunRolesRepository(db) ctx := context.Background() @@ -307,6 +321,8 @@ func TestBunRolesRepositoryGetRoleByID(t *testing.T) { func TestBunRolesRepositoryGetRoleByName(t *testing.T) { t.Parallel() + r1 := uuid.New().String() + tests := []struct { name string roleName string @@ -327,7 +343,7 @@ func TestBunRolesRepositoryGetRoleByName(t *testing.T) { { name: "success", roleName: "editor", - seedRole: &types.Role{ID: "r1", Name: "editor", Description: new("Editor role"), Weight: 20, IsSystem: true}, + seedRole: &types.Role{ID: r1, Name: "editor", Description: new("Editor role"), Weight: 20, IsSystem: true}, wantName: "editor", wantDesc: new("Editor role"), wantSystem: true, @@ -336,7 +352,7 @@ func TestBunRolesRepositoryGetRoleByName(t *testing.T) { { name: "query error", roleName: "editor", - seedRole: &types.Role{ID: "r1", Name: "editor", Description: new("Editor role"), Weight: 10, IsSystem: false}, + seedRole: &types.Role{ID: r1, Name: "editor", Description: new("Editor role"), Weight: 10, IsSystem: false}, wantErr: true, wantErrMsg: "failed to get role by name", }, @@ -346,7 +362,9 @@ func TestBunRolesRepositoryGetRoleByName(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + user1ID := uuid.New().String() + user2ID := uuid.New().String() + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := NewBunRolesRepository(db) ctx := context.Background() @@ -404,6 +422,12 @@ func TestBunRolesRepositoryGetRoleByName(t *testing.T) { func TestBunRolesRepositoryUpdateRole(t *testing.T) { t.Parallel() + r1 := uuid.New().String() + r2 := uuid.New().String() + r3 := uuid.New().String() + r4 := uuid.New().String() + r5 := uuid.New().String() + tests := []struct { name string seedRole *types.Role @@ -419,15 +443,15 @@ func TestBunRolesRepositoryUpdateRole(t *testing.T) { }{ { name: "missing role", - roleID: "missing", + roleID: "00000000-0000-0000-0000-000000000000", nameValue: new("updated"), description: new("updated description"), wantUpdated: false, }, { name: "update name only", - seedRole: &types.Role{ID: "r1", Name: "editor", Description: new("Editor role"), IsSystem: false, Weight: 10}, - roleID: "r1", + seedRole: &types.Role{ID: r1, Name: "editor", Description: new("Editor role"), IsSystem: false, Weight: 10}, + roleID: r1, nameValue: new("editor-updated"), wantUpdated: true, wantName: new("editor-updated"), @@ -436,8 +460,8 @@ func TestBunRolesRepositoryUpdateRole(t *testing.T) { }, { name: "update description only", - seedRole: &types.Role{ID: "r2", Name: "viewer", Description: new("Viewer role"), IsSystem: false, Weight: 10}, - roleID: "r2", + seedRole: &types.Role{ID: r2, Name: "viewer", Description: new("Viewer role"), IsSystem: false, Weight: 10}, + roleID: r2, description: new("Viewer role updated"), wantUpdated: true, wantName: new("viewer"), @@ -446,8 +470,8 @@ func TestBunRolesRepositoryUpdateRole(t *testing.T) { }, { name: "update name and description", - seedRole: &types.Role{ID: "r3", Name: "author", Description: new("Author role"), IsSystem: false, Weight: 10}, - roleID: "r3", + seedRole: &types.Role{ID: r3, Name: "author", Description: new("Author role"), IsSystem: false, Weight: 10}, + roleID: r3, nameValue: new("author-updated"), description: new("Author role updated"), wantUpdated: true, @@ -457,8 +481,8 @@ func TestBunRolesRepositoryUpdateRole(t *testing.T) { }, { name: "update with no fields still updates timestamp", - seedRole: &types.Role{ID: "r4", Name: "reviewer", Description: new("Reviewer role"), IsSystem: false, Weight: 10}, - roleID: "r4", + seedRole: &types.Role{ID: r4, Name: "reviewer", Description: new("Reviewer role"), IsSystem: false, Weight: 10}, + roleID: r4, wantUpdated: true, wantName: new("reviewer"), wantDesc: new("Reviewer role"), @@ -466,7 +490,7 @@ func TestBunRolesRepositoryUpdateRole(t *testing.T) { }, { name: "query error", - roleID: "r5", + roleID: r5, nameValue: new("updated"), wantErrMsg: "sql: database is closed", }, @@ -476,7 +500,9 @@ func TestBunRolesRepositoryUpdateRole(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + user1ID := uuid.New().String() + user2ID := uuid.New().String() + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := NewBunRolesRepository(db) ctx := context.Background() @@ -543,6 +569,9 @@ func TestBunRolesRepositoryUpdateRole(t *testing.T) { func TestBunRolesRepositoryDeleteRole(t *testing.T) { t.Parallel() + r1 := uuid.New().String() + r5 := uuid.New().String() + tests := []struct { name string seedRole *types.Role @@ -552,18 +581,18 @@ func TestBunRolesRepositoryDeleteRole(t *testing.T) { }{ { name: "missing role", - roleID: "missing", + roleID: "00000000-0000-0000-0000-000000000000", wantDeleted: false, }, { name: "success", - seedRole: &types.Role{ID: "r1", Name: "editor", Description: new("Editor role"), Weight: 10, IsSystem: false}, - roleID: "r1", + seedRole: &types.Role{ID: r1, Name: "editor", Description: new("Editor role"), Weight: 10, IsSystem: false}, + roleID: r1, wantDeleted: true, }, { name: "query error", - roleID: "r5", + roleID: r5, wantErrMsg: "sql: database is closed", }, } @@ -572,7 +601,9 @@ func TestBunRolesRepositoryDeleteRole(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + user1ID := uuid.New().String() + user2ID := uuid.New().String() + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := NewBunRolesRepository(db) ctx := context.Background() diff --git a/plugins/access-control/repositories/user_permissions_repository_test.go b/plugins/access-control/repositories/user_permissions_repository_test.go index 6ece6f6a..e6d62185 100644 --- a/plugins/access-control/repositories/user_permissions_repository_test.go +++ b/plugins/access-control/repositories/user_permissions_repository_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/google/uuid" + plugintests "github.com/Authula/authula/plugins/access-control/tests" "github.com/Authula/authula/plugins/access-control/types" ) @@ -14,6 +16,13 @@ func TestBunUserPermissionsRepositoryGetUserPermissions(t *testing.T) { description := new(string) *description = "Read users" + user1ID := uuid.New().String() + user2ID := uuid.New().String() + role1ID := uuid.New().String() + role2ID := uuid.New().String() + perm1ID := uuid.New().String() + perm2ID := uuid.New().String() + tests := []struct { name string seed func(*BunRolesRepository, *BunPermissionsRepository, *BunRolePermissionsRepository, *BunUserRolesRepository, context.Context) @@ -24,39 +33,39 @@ func TestBunUserPermissionsRepositoryGetUserPermissions(t *testing.T) { }{ { name: "empty result", - userID: "missing-user", + userID: "00000000-0000-0000-0000-000000000000", wantEmpty: true, }, { name: "aggregates permissions across roles and ignores expired roles", - userID: "u1", + userID: user1ID, seed: func(rolesRepo *BunRolesRepository, permissionsRepo *BunPermissionsRepository, rolePermissionsRepo *BunRolePermissionsRepository, userRolesRepo *BunUserRolesRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "role-1", Name: "editor", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: role1ID, Name: "editor", Weight: 10}); err != nil { panic(err) } - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "role-2", Name: "viewer", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: role2ID, Name: "viewer", Weight: 10}); err != nil { panic(err) } - if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: "perm-1", Key: "users.read", Description: description}); err != nil { + if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: perm1ID, Key: "users.read", Description: description}); err != nil { panic(err) } - if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: "perm-2", Key: "users.write"}); err != nil { + if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: perm2ID, Key: "users.write"}); err != nil { panic(err) } - grantedBy := "u2" - if err := rolePermissionsRepo.AddRolePermission(ctx, "role-1", "perm-1", &grantedBy); err != nil { + grantedBy := user2ID + if err := rolePermissionsRepo.AddRolePermission(ctx, role1ID, perm1ID, &grantedBy); err != nil { panic(err) } - if err := rolePermissionsRepo.AddRolePermission(ctx, "role-2", "perm-1", nil); err != nil { + if err := rolePermissionsRepo.AddRolePermission(ctx, role2ID, perm1ID, nil); err != nil { panic(err) } - if err := rolePermissionsRepo.AddRolePermission(ctx, "role-2", "perm-2", &grantedBy); err != nil { + if err := rolePermissionsRepo.AddRolePermission(ctx, role2ID, perm2ID, &grantedBy); err != nil { panic(err) } - if err := userRolesRepo.AssignUserRole(ctx, "u1", "role-1", &grantedBy, nil); err != nil { + if err := userRolesRepo.AssignUserRole(ctx, user1ID, role1ID, &grantedBy, nil); err != nil { panic(err) } - if err := userRolesRepo.AssignUserRole(ctx, "u1", "role-2", nil, nil); err != nil { + if err := userRolesRepo.AssignUserRole(ctx, user1ID, role2ID, nil, nil); err != nil { panic(err) } }, @@ -69,7 +78,7 @@ func TestBunUserPermissionsRepositoryGetUserPermissions(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) rolesRepo := NewBunRolesRepository(db) permissionsRepo := NewBunPermissionsRepository(db) rolePermissionsRepo := NewBunRolePermissionsRepository(db) @@ -118,6 +127,9 @@ func TestBunUserPermissionsRepositoryGetUserPermissions(t *testing.T) { func TestBunUserPermissionsRepositoryHasPermissions(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + tests := []struct { name string seed func(*BunRolesRepository, *BunPermissionsRepository, *BunRolePermissionsRepository, *BunUserRolesRepository, context.Context) @@ -127,25 +139,27 @@ func TestBunUserPermissionsRepositoryHasPermissions(t *testing.T) { }{ { name: "empty permission list returns true", - userID: "u1", + userID: user1ID, permissionKeys: []string{}, wantHasPerms: true, }, { name: "missing permission returns false", - userID: "u1", + userID: user1ID, permissionKeys: []string{"users.read", "users.delete"}, seed: func(rolesRepo *BunRolesRepository, permissionsRepo *BunPermissionsRepository, rolePermissionsRepo *BunRolePermissionsRepository, userRolesRepo *BunUserRolesRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "role-1", Name: "editor", Weight: 10}); err != nil { + roleID := uuid.New().String() + permID := uuid.New().String() + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: roleID, Name: "editor", Weight: 10}); err != nil { panic(err) } - if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: "perm-1", Key: "users.read"}); err != nil { + if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: permID, Key: "users.read"}); err != nil { panic(err) } - if err := rolePermissionsRepo.AddRolePermission(ctx, "role-1", "perm-1", nil); err != nil { + if err := rolePermissionsRepo.AddRolePermission(ctx, roleID, permID, nil); err != nil { panic(err) } - if err := userRolesRepo.AssignUserRole(ctx, "u1", "role-1", nil, nil); err != nil { + if err := userRolesRepo.AssignUserRole(ctx, user1ID, roleID, nil, nil); err != nil { panic(err) } }, @@ -153,20 +167,22 @@ func TestBunUserPermissionsRepositoryHasPermissions(t *testing.T) { }, { name: "success", - userID: "u1", + userID: user1ID, permissionKeys: []string{"users.read"}, seed: func(rolesRepo *BunRolesRepository, permissionsRepo *BunPermissionsRepository, rolePermissionsRepo *BunRolePermissionsRepository, userRolesRepo *BunUserRolesRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "role-1", Name: "editor", Weight: 10}); err != nil { + roleID := uuid.New().String() + permID := uuid.New().String() + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: roleID, Name: "editor", Weight: 10}); err != nil { panic(err) } - if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: "perm-1", Key: "users.read"}); err != nil { + if err := permissionsRepo.CreatePermission(ctx, &types.Permission{ID: permID, Key: "users.read"}); err != nil { panic(err) } - grantedBy := "u2" - if err := rolePermissionsRepo.AddRolePermission(ctx, "role-1", "perm-1", &grantedBy); err != nil { + grantedBy := user2ID + if err := rolePermissionsRepo.AddRolePermission(ctx, roleID, permID, &grantedBy); err != nil { panic(err) } - if err := userRolesRepo.AssignUserRole(ctx, "u1", "role-1", nil, nil); err != nil { + if err := userRolesRepo.AssignUserRole(ctx, user1ID, roleID, nil, nil); err != nil { panic(err) } }, @@ -178,7 +194,7 @@ func TestBunUserPermissionsRepositoryHasPermissions(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) rolesRepo := NewBunRolesRepository(db) permissionsRepo := NewBunPermissionsRepository(db) rolePermissionsRepo := NewBunRolePermissionsRepository(db) diff --git a/plugins/access-control/repositories/user_roles_repository_test.go b/plugins/access-control/repositories/user_roles_repository_test.go index dc47c7e8..d3669b08 100644 --- a/plugins/access-control/repositories/user_roles_repository_test.go +++ b/plugins/access-control/repositories/user_roles_repository_test.go @@ -3,10 +3,11 @@ package repositories import ( "context" "reflect" - "strings" "testing" "time" + "github.com/google/uuid" + plugintests "github.com/Authula/authula/plugins/access-control/tests" "github.com/Authula/authula/plugins/access-control/types" ) @@ -17,7 +18,11 @@ func TestBunUserRolesRepositoryGetUserRoles(t *testing.T) { now := time.Now().UTC() futureExpiry := time.Unix(now.Add(24*time.Hour).Unix(), 0).UTC() roleDescription := new("Editor role") - assignedBy := new("u2") + user1ID := uuid.New().String() + user2ID := uuid.New().String() + assignedBy := &user2ID + r1 := uuid.New().String() + r2 := uuid.New().String() tests := []struct { name string @@ -27,29 +32,29 @@ func TestBunUserRolesRepositoryGetUserRoles(t *testing.T) { }{ { name: "empty result", - userID: "missing-user", + userID: "00000000-0000-0000-0000-000000000000", wantRoles: []types.UserRoleInfo{}, }, { name: "returns assigned roles ordered by role name", - userID: "u1", + userID: user1ID, seed: func(rolesRepo *BunRolesRepository, userRolesRepo *BunUserRolesRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "r2", Name: "viewer", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: r2, Name: "viewer", Weight: 10}); err != nil { panic(err) } - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "r1", Name: "editor", Description: roleDescription, Weight: 30}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: r1, Name: "editor", Description: roleDescription, Weight: 30}); err != nil { panic(err) } - if err := userRolesRepo.AssignUserRole(ctx, "u1", "r1", assignedBy, &futureExpiry); err != nil { + if err := userRolesRepo.AssignUserRole(ctx, user1ID, r1, assignedBy, &futureExpiry); err != nil { panic(err) } - if err := userRolesRepo.AssignUserRole(ctx, "u1", "r2", nil, nil); err != nil { + if err := userRolesRepo.AssignUserRole(ctx, user1ID, r2, nil, nil); err != nil { panic(err) } }, wantRoles: []types.UserRoleInfo{ { - RoleID: "r1", + RoleID: r1, RoleName: "editor", RoleDescription: roleDescription, RoleWeight: 30, @@ -57,7 +62,7 @@ func TestBunUserRolesRepositoryGetUserRoles(t *testing.T) { ExpiresAt: &futureExpiry, }, { - RoleID: "r2", + RoleID: r2, RoleName: "viewer", RoleDescription: nil, RoleWeight: 10, @@ -72,7 +77,7 @@ func TestBunUserRolesRepositoryGetUserRoles(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) rolesRepo := NewBunRolesRepository(db) userRolesRepo := NewBunUserRolesRepository(db) ctx := context.Background() @@ -112,6 +117,11 @@ func TestBunUserRolesRepositoryGetUserRoles(t *testing.T) { func TestBunUserRolesRepositoryReplaceUserRoles(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + r1 := uuid.New().String() + r2 := uuid.New().String() + tests := []struct { name string seed func(*BunRolesRepository, *BunUserRolesRepository, context.Context) @@ -121,24 +131,24 @@ func TestBunUserRolesRepositoryReplaceUserRoles(t *testing.T) { }{ { name: "replaces all roles", - userID: "u1", - roleIDs: []string{"r2", "r1"}, + userID: user1ID, + roleIDs: []string{r2, r1}, seed: func(rolesRepo *BunRolesRepository, userRolesRepo *BunUserRolesRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "r1", Name: "editor", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: r1, Name: "editor", Weight: 10}); err != nil { panic(err) } - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "r2", Name: "viewer", Weight: 20}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: r2, Name: "viewer", Weight: 20}); err != nil { panic(err) } - if err := userRolesRepo.AssignUserRole(ctx, "u1", "r1", nil, nil); err != nil { + if err := userRolesRepo.AssignUserRole(ctx, user1ID, r1, nil, nil); err != nil { panic(err) } }, - wantRoleIDs: []string{"r2", "r1"}, + wantRoleIDs: []string{r2, r1}, }, { name: "empty list clears roles", - userID: "u1", + userID: user1ID, roleIDs: []string{}, wantRoleIDs: []string{}, }, @@ -148,7 +158,7 @@ func TestBunUserRolesRepositoryReplaceUserRoles(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) rolesRepo := NewBunRolesRepository(db) userRolesRepo := NewBunUserRolesRepository(db) ctx := context.Background() @@ -185,6 +195,9 @@ func TestBunUserRolesRepositoryAssignUserRole(t *testing.T) { now := time.Now().UTC() futureExpiry := time.Unix(now.Add(24*time.Hour).Unix(), 0).UTC() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + r1 := uuid.New().String() tests := []struct { name string @@ -196,24 +209,24 @@ func TestBunUserRolesRepositoryAssignUserRole(t *testing.T) { }{ { name: "success", - userID: "u1", - roleID: "r1", + userID: user1ID, + roleID: r1, expiresAt: &futureExpiry, seed: func(rolesRepo *BunRolesRepository, userRolesRepo *BunUserRolesRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "r1", Name: "editor", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: r1, Name: "editor", Weight: 10}); err != nil { panic(err) } }, }, { name: "duplicate assignment returns conflict", - userID: "u1", - roleID: "r1", + userID: user1ID, + roleID: r1, seed: func(rolesRepo *BunRolesRepository, userRolesRepo *BunUserRolesRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "r1", Name: "editor", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: r1, Name: "editor", Weight: 10}); err != nil { panic(err) } - if err := userRolesRepo.AssignUserRole(ctx, "u1", "r1", nil, nil); err != nil { + if err := userRolesRepo.AssignUserRole(ctx, user1ID, r1, nil, nil); err != nil { panic(err) } }, @@ -224,7 +237,7 @@ func TestBunUserRolesRepositoryAssignUserRole(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) rolesRepo := NewBunRolesRepository(db) userRolesRepo := NewBunUserRolesRepository(db) ctx := context.Background() @@ -238,9 +251,6 @@ func TestBunUserRolesRepositoryAssignUserRole(t *testing.T) { if err == nil { t.Fatal("expected error, got nil") } - if !strings.Contains(err.Error(), "UNIQUE constraint failed: access_control_user_roles.user_id, access_control_user_roles.role_id") { - t.Fatalf("expected raw unique constraint error, got %v", err) - } return } if err != tc.wantErr { @@ -274,6 +284,10 @@ func TestBunUserRolesRepositoryAssignUserRole(t *testing.T) { func TestBunUserRolesRepositoryRemoveUserRole(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + r1 := uuid.New().String() + tests := []struct { name string seed func(*BunRolesRepository, *BunUserRolesRepository, context.Context) @@ -283,13 +297,13 @@ func TestBunUserRolesRepositoryRemoveUserRole(t *testing.T) { }{ { name: "success", - userID: "u1", - roleID: "r1", + userID: user1ID, + roleID: r1, seed: func(rolesRepo *BunRolesRepository, userRolesRepo *BunUserRolesRepository, ctx context.Context) { - if err := rolesRepo.CreateRole(ctx, &types.Role{ID: "r1", Name: "editor", Weight: 10}); err != nil { + if err := rolesRepo.CreateRole(ctx, &types.Role{ID: r1, Name: "editor", Weight: 10}); err != nil { panic(err) } - if err := userRolesRepo.AssignUserRole(ctx, "u1", "r1", nil, nil); err != nil { + if err := userRolesRepo.AssignUserRole(ctx, user1ID, r1, nil, nil); err != nil { panic(err) } }, @@ -301,7 +315,7 @@ func TestBunUserRolesRepositoryRemoveUserRole(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) rolesRepo := NewBunRolesRepository(db) userRolesRepo := NewBunUserRolesRepository(db) ctx := context.Background() diff --git a/plugins/access-control/tests/test_helpers.go b/plugins/access-control/tests/test_helpers.go index 8bf90269..0ba0bb0e 100644 --- a/plugins/access-control/tests/test_helpers.go +++ b/plugins/access-control/tests/test_helpers.go @@ -2,14 +2,11 @@ package tests import ( "context" - "database/sql" "testing" "time" - _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/mock" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/sqlitedialect" internaltests "github.com/Authula/authula/internal/tests" "github.com/Authula/authula/migrations" @@ -185,18 +182,10 @@ func (m *MockUserPermissionsRepository) HasPermissions(ctx context.Context, user return args.Bool(0), args.Error(1) } -func SetupRepoDB(t *testing.T) *bun.DB { +func SetupRepoDB(t *testing.T, user1ID, user2ID string) *bun.DB { t.Helper() - sqldb, err := sql.Open("sqlite3", ":memory:") - if err != nil { - t.Fatalf("failed to open sqlite: %v", err) - } - - db := bun.NewDB(sqldb, sqlitedialect.New()) - t.Cleanup(func() { - _ = db.Close() - }) + db := internaltests.NewIntegrationTestDB(t) ctx := context.Background() @@ -205,7 +194,7 @@ func SetupRepoDB(t *testing.T) *bun.DB { t.Fatalf("failed to create migrator: %v", err) } - coreSet, err := migrations.CoreMigrationSet("sqlite") + coreSet, err := migrations.CoreMigrationSet() if err != nil { t.Fatalf("failed to build core migration set: %v", err) } @@ -213,17 +202,17 @@ func SetupRepoDB(t *testing.T) *bun.DB { accessControlSet := migrations.MigrationSet{ PluginID: models.PluginAccessControl.String(), DependsOn: []string{migrations.CorePluginID}, - Migrations: accesscontrolmigrations.ForProvider("sqlite"), + Migrations: accesscontrolmigrations.Migrations(), } if err := migrator.Migrate(ctx, []migrations.MigrationSet{coreSet, accessControlSet}); err != nil { t.Fatalf("failed to run migrations: %v", err) } - if _, err := db.ExecContext(ctx, `INSERT INTO users (id, name, email, email_verified, metadata) VALUES ('u1', 'User One', 'u1@example.com', 1, '{}')`); err != nil { + if _, err := db.ExecContext(ctx, `INSERT INTO users (id, name, email, email_verified, metadata) VALUES (?, ?, ?, true, '{}')`, user1ID, "User One", "u1@example.com"); err != nil { t.Fatalf("failed to seed user u1: %v", err) } - if _, err := db.ExecContext(ctx, `INSERT INTO users (id, name, email, email_verified, metadata) VALUES ('u2', 'User Two', 'u2@example.com', 1, '{}')`); err != nil { + if _, err := db.ExecContext(ctx, `INSERT INTO users (id, name, email, email_verified, metadata) VALUES (?, ?, ?, true, '{}')`, user2ID, "User Two", "u2@example.com"); err != nil { t.Fatalf("failed to seed user u2: %v", err) } diff --git a/plugins/admin/migrations.go b/plugins/admin/migrations.go index 114a0aa9..cb4bb636 100644 --- a/plugins/admin/migrations.go +++ b/plugins/admin/migrations.go @@ -8,103 +8,21 @@ import ( "github.com/Authula/authula/migrations" ) -func adminMigrationsForProvider(provider string) []migrations.Migration { - return migrations.ForProvider(provider, migrations.ProviderVariants{ - "sqlite": func() []migrations.Migration { return []migrations.Migration{adminSQLiteInitial()} }, - "postgres": func() []migrations.Migration { return []migrations.Migration{adminPostgresInitial()} }, - "mysql": func() []migrations.Migration { return []migrations.Migration{adminMySQLInitial()} }, - }) -} - -func adminSQLiteInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260222000000_admin_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `PRAGMA foreign_keys = ON;`, - `CREATE TABLE IF NOT EXISTS admin_impersonations ( - id TEXT PRIMARY KEY, - actor_user_id TEXT NOT NULL, - target_user_id TEXT NOT NULL, - actor_session_id TEXT, - impersonation_session_id TEXT, - reason TEXT NOT NULL, - started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP NOT NULL, - ended_at TIMESTAMP, - ended_by_user_id TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (actor_user_id) REFERENCES users(id) ON DELETE CASCADE, - FOREIGN KEY (target_user_id) REFERENCES users(id) ON DELETE CASCADE, - FOREIGN KEY (ended_by_user_id) REFERENCES users(id) ON DELETE SET NULL, - FOREIGN KEY (actor_session_id) REFERENCES sessions(id) ON DELETE SET NULL, - FOREIGN KEY (impersonation_session_id) REFERENCES sessions(id) ON DELETE SET NULL, - CHECK (actor_user_id != target_user_id) - );`, - `CREATE INDEX IF NOT EXISTS idx_admin_impersonations_actor_user_id ON admin_impersonations(actor_user_id);`, - `CREATE INDEX IF NOT EXISTS idx_admin_impersonations_target_user_id ON admin_impersonations(target_user_id);`, - `CREATE INDEX IF NOT EXISTS idx_admin_impersonations_expires_at_ended_at ON admin_impersonations(expires_at, ended_at);`, - `CREATE TABLE IF NOT EXISTS admin_user_states ( - user_id TEXT PRIMARY KEY, - banned BOOLEAN NOT NULL DEFAULT 0, - banned_at TIMESTAMP, - banned_until TIMESTAMP, - banned_reason TEXT, - banned_by_user_id TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - FOREIGN KEY (banned_by_user_id) REFERENCES users(id) ON DELETE SET NULL - );`, - `CREATE INDEX IF NOT EXISTS idx_admin_user_states_banned_banned_until ON admin_user_states(banned, banned_until);`, - `CREATE TABLE IF NOT EXISTS admin_session_states ( - session_id TEXT PRIMARY KEY, - revoked_at TIMESTAMP, - revoked_reason TEXT, - revoked_by_user_id TEXT, - impersonator_user_id TEXT, - impersonator_session_id TEXT, - impersonation_reason TEXT, - impersonation_expires_at TIMESTAMP, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE, - FOREIGN KEY (revoked_by_user_id) REFERENCES users(id) ON DELETE SET NULL, - FOREIGN KEY (impersonator_user_id) REFERENCES users(id) ON DELETE SET NULL - );`, - `CREATE INDEX IF NOT EXISTS idx_admin_session_states_revoked_at_impersonation_expires_at ON admin_session_states(revoked_at, impersonation_expires_at);`, - `CREATE INDEX IF NOT EXISTS idx_admin_session_states_impersonator_user_id ON admin_session_states(impersonator_user_id);`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS admin_session_states;`, - `DROP TABLE IF EXISTS admin_user_states;`, - `DROP TABLE IF EXISTS admin_impersonations;`, - ) - }, - } -} - -func adminPostgresInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260222000000_admin_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE OR REPLACE FUNCTION admin_set_updated_at_fn() RETURNS TRIGGER AS $$ +func adminMigrations() []migrations.Migration { + return []migrations.Migration{ + { + Version: "20260222000000_admin_initial", + Up: func(ctx context.Context, tx bun.Tx) error { + return migrations.ExecStatements( + ctx, + tx, + `CREATE OR REPLACE FUNCTION admin_set_updated_at_fn() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql;`, - `CREATE TABLE IF NOT EXISTS admin_impersonations ( + `CREATE TABLE IF NOT EXISTS admin_impersonations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), actor_user_id UUID NOT NULL, target_user_id UUID NOT NULL, @@ -124,15 +42,15 @@ func adminPostgresInitial() migrations.Migration { CONSTRAINT fk_admin_impersonations_impersonation_session FOREIGN KEY (impersonation_session_id) REFERENCES sessions(id) ON DELETE SET NULL, CONSTRAINT chk_admin_impersonation_actor_target CHECK (actor_user_id <> target_user_id) );`, - `DROP TRIGGER IF EXISTS update_admin_impersonations_updated_at_trigger ON admin_impersonations;`, - `CREATE TRIGGER update_admin_impersonations_updated_at_trigger + `DROP TRIGGER IF EXISTS update_admin_impersonations_updated_at_trigger ON admin_impersonations;`, + `CREATE TRIGGER update_admin_impersonations_updated_at_trigger BEFORE UPDATE ON admin_impersonations FOR EACH ROW EXECUTE FUNCTION admin_set_updated_at_fn();`, - `CREATE INDEX IF NOT EXISTS idx_admin_impersonations_actor_user_id ON admin_impersonations(actor_user_id);`, - `CREATE INDEX IF NOT EXISTS idx_admin_impersonations_target_user_id ON admin_impersonations(target_user_id);`, - `CREATE INDEX IF NOT EXISTS idx_admin_impersonations_expires_at_ended_at ON admin_impersonations(expires_at, ended_at);`, - `CREATE TABLE IF NOT EXISTS admin_user_states ( + `CREATE INDEX IF NOT EXISTS idx_admin_impersonations_actor_user_id ON admin_impersonations(actor_user_id);`, + `CREATE INDEX IF NOT EXISTS idx_admin_impersonations_target_user_id ON admin_impersonations(target_user_id);`, + `CREATE INDEX IF NOT EXISTS idx_admin_impersonations_expires_at_ended_at ON admin_impersonations(expires_at, ended_at);`, + `CREATE TABLE IF NOT EXISTS admin_user_states ( user_id UUID PRIMARY KEY, banned BOOLEAN NOT NULL DEFAULT FALSE, banned_at TIMESTAMP WITH TIME ZONE, @@ -144,13 +62,13 @@ func adminPostgresInitial() migrations.Migration { CONSTRAINT fk_admin_user_states_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, CONSTRAINT fk_admin_user_states_banned_by FOREIGN KEY (banned_by_user_id) REFERENCES users(id) ON DELETE SET NULL );`, - `DROP TRIGGER IF EXISTS update_admin_user_states_updated_at_trigger ON admin_user_states;`, - `CREATE TRIGGER update_admin_user_states_updated_at_trigger + `DROP TRIGGER IF EXISTS update_admin_user_states_updated_at_trigger ON admin_user_states;`, + `CREATE TRIGGER update_admin_user_states_updated_at_trigger BEFORE UPDATE ON admin_user_states FOR EACH ROW EXECUTE FUNCTION admin_set_updated_at_fn();`, - `CREATE INDEX IF NOT EXISTS idx_admin_user_states_banned_banned_until ON admin_user_states(banned, banned_until);`, - `CREATE TABLE IF NOT EXISTS admin_session_states ( + `CREATE INDEX IF NOT EXISTS idx_admin_user_states_banned_banned_until ON admin_user_states(banned, banned_until);`, + `CREATE TABLE IF NOT EXISTS admin_session_states ( session_id UUID PRIMARY KEY, revoked_at TIMESTAMP WITH TIME ZONE, revoked_reason TEXT, @@ -165,101 +83,28 @@ func adminPostgresInitial() migrations.Migration { CONSTRAINT fk_admin_session_states_revoked_by FOREIGN KEY (revoked_by_user_id) REFERENCES users(id) ON DELETE SET NULL, CONSTRAINT fk_admin_session_states_impersonator FOREIGN KEY (impersonator_user_id) REFERENCES users(id) ON DELETE SET NULL );`, - `DROP TRIGGER IF EXISTS update_admin_session_states_updated_at_trigger ON admin_session_states;`, - `CREATE TRIGGER update_admin_session_states_updated_at_trigger + `DROP TRIGGER IF EXISTS update_admin_session_states_updated_at_trigger ON admin_session_states;`, + `CREATE TRIGGER update_admin_session_states_updated_at_trigger BEFORE UPDATE ON admin_session_states FOR EACH ROW EXECUTE FUNCTION admin_set_updated_at_fn();`, - `CREATE INDEX IF NOT EXISTS idx_admin_session_states_revoked_at_impersonation_expires_at ON admin_session_states(revoked_at, impersonation_expires_at);`, - `CREATE INDEX IF NOT EXISTS idx_admin_session_states_impersonator_user_id ON admin_session_states(impersonator_user_id);`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TRIGGER IF EXISTS update_admin_session_states_updated_at_trigger ON admin_session_states;`, - `DROP TABLE IF EXISTS admin_session_states;`, - `DROP TRIGGER IF EXISTS update_admin_user_states_updated_at_trigger ON admin_user_states;`, - `DROP TABLE IF EXISTS admin_user_states;`, - `DROP TRIGGER IF EXISTS update_admin_impersonations_updated_at_trigger ON admin_impersonations;`, - `DROP TABLE IF EXISTS admin_impersonations;`, - `DROP FUNCTION IF EXISTS admin_set_updated_at_fn();`, - ) - }, - } -} - -func adminMySQLInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260222000000_admin_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS admin_impersonations ( - id BINARY(16) NOT NULL PRIMARY KEY, - actor_user_id BINARY(16) NOT NULL, - target_user_id BINARY(16) NOT NULL, - actor_session_id BINARY(16) NULL, - impersonation_session_id BINARY(16) NULL, - reason TEXT NOT NULL, - started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP NOT NULL, - ended_at TIMESTAMP NULL, - ended_by_user_id BINARY(16) NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_admin_impersonations_actor_user FOREIGN KEY (actor_user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT fk_admin_impersonations_target_user FOREIGN KEY (target_user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT fk_admin_impersonations_actor_session FOREIGN KEY (actor_session_id) REFERENCES sessions(id) ON DELETE SET NULL, - CONSTRAINT fk_admin_impersonations_impersonation_session FOREIGN KEY (impersonation_session_id) REFERENCES sessions(id) ON DELETE SET NULL, - CONSTRAINT fk_admin_impersonations_ended_by_user FOREIGN KEY (ended_by_user_id) REFERENCES users(id) ON DELETE SET NULL, - CONSTRAINT chk_admin_impersonation_actor_target CHECK (actor_user_id <> target_user_id), - INDEX idx_admin_impersonations_actor_user_id (actor_user_id), - INDEX idx_admin_impersonations_target_user_id (target_user_id), - INDEX idx_admin_impersonations_expires_at_ended_at (expires_at, ended_at) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - `CREATE TABLE IF NOT EXISTS admin_user_states ( - user_id BINARY(16) NOT NULL PRIMARY KEY, - banned TINYINT(1) NOT NULL DEFAULT 0, - banned_at TIMESTAMP NULL, - banned_until TIMESTAMP NULL, - banned_reason TEXT NULL, - banned_by_user_id BINARY(16) NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_admin_user_states_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT fk_admin_user_states_banned_by FOREIGN KEY (banned_by_user_id) REFERENCES users(id) ON DELETE SET NULL, - INDEX idx_admin_user_states_banned_banned_until (banned, banned_until) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - `CREATE TABLE IF NOT EXISTS admin_session_states ( - session_id BINARY(16) NOT NULL PRIMARY KEY, - revoked_at TIMESTAMP NULL, - revoked_reason TEXT NULL, - revoked_by_user_id BINARY(16) NULL, - impersonator_user_id BINARY(16) NULL, - impersonator_session_id BINARY(16) NULL, - impersonation_reason TEXT NULL, - impersonation_expires_at TIMESTAMP NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_admin_session_states_session FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE, - CONSTRAINT fk_admin_session_states_revoked_by FOREIGN KEY (revoked_by_user_id) REFERENCES users(id) ON DELETE SET NULL, - CONSTRAINT fk_admin_session_states_impersonator FOREIGN KEY (impersonator_user_id) REFERENCES users(id) ON DELETE SET NULL, - INDEX idx_admin_session_states_revoked_at_impersonation_expires_at (revoked_at, impersonation_expires_at), - INDEX idx_admin_session_states_impersonator_user_id (impersonator_user_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS admin_user_states;`, - `DROP TABLE IF EXISTS admin_session_states;`, - `DROP TABLE IF EXISTS admin_impersonations;`, - ) + `CREATE INDEX IF NOT EXISTS idx_admin_session_states_revoked_at_impersonation_expires_at ON admin_session_states(revoked_at, impersonation_expires_at);`, + `CREATE INDEX IF NOT EXISTS idx_admin_session_states_impersonator_user_id ON admin_session_states(impersonator_user_id);`, + ) + }, + Down: func(ctx context.Context, tx bun.Tx) error { + return migrations.ExecStatements( + ctx, + tx, + `DROP TRIGGER IF EXISTS update_admin_session_states_updated_at_trigger ON admin_session_states;`, + `DROP TABLE IF EXISTS admin_session_states;`, + `DROP TRIGGER IF EXISTS update_admin_user_states_updated_at_trigger ON admin_user_states;`, + `DROP TABLE IF EXISTS admin_user_states;`, + `DROP TRIGGER IF EXISTS update_admin_impersonations_updated_at_trigger ON admin_impersonations;`, + `DROP TABLE IF EXISTS admin_impersonations;`, + `DROP FUNCTION IF EXISTS admin_set_updated_at_fn();`, + ) + }, }, } } diff --git a/plugins/admin/plugin.go b/plugins/admin/plugin.go index f6e24492..423e6a83 100644 --- a/plugins/admin/plugin.go +++ b/plugins/admin/plugin.go @@ -111,8 +111,8 @@ func (p *AdminPlugin) Init(ctx *models.PluginContext) error { return nil } -func (p *AdminPlugin) Migrations(provider string) []migrations.Migration { - return adminMigrationsForProvider(provider) +func (p *AdminPlugin) Migrations() []migrations.Migration { + return adminMigrations() } func (p *AdminPlugin) DependsOn() []string { diff --git a/plugins/admin/repositories/impersonation_repository_test.go b/plugins/admin/repositories/impersonation_repository_test.go index 1f8313cb..2394f847 100644 --- a/plugins/admin/repositories/impersonation_repository_test.go +++ b/plugins/admin/repositories/impersonation_repository_test.go @@ -15,7 +15,7 @@ import ( func setupImpersonationRepo(t *testing.T) (*repositories.BunImpersonationRepository, *bun.DB, func()) { t.Helper() - db, _ := tests.NewIntegrationTestDBFromEnv(t) + db := tests.NewIntegrationTestDB(t) ctx := context.Background() if _, err := db.NewCreateTable().Model((*types.Impersonation)(nil)).IfNotExists().Exec(ctx); err != nil { diff --git a/plugins/admin/repositories/session_state_repository.go b/plugins/admin/repositories/session_state_repository.go index 4e385292..be320c43 100644 --- a/plugins/admin/repositories/session_state_repository.go +++ b/plugins/admin/repositories/session_state_repository.go @@ -7,9 +7,6 @@ import ( "time" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/mysqldialect" - "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/dialect/sqlitedialect" "github.com/Authula/authula/models" "github.com/Authula/authula/plugins/admin/types" @@ -41,64 +38,17 @@ func (r *BunSessionStateRepository) Upsert(ctx context.Context, state *types.Adm now := time.Now().UTC() state.UpdatedAt = now - var err error - dialect := r.db.Dialect() - - // Check which database dialect we're using - switch dialect.(type) { - case *mysqldialect.Dialect: - // MySQL uses ON DUPLICATE KEY UPDATE with VALUES() function - _, err = r.db.NewInsert(). - Model(state). - On("DUPLICATE KEY UPDATE"). - Set("revoked_at = VALUES(revoked_at)"). - Set("revoked_reason = VALUES(revoked_reason)"). - Set("revoked_by_user_id = VALUES(revoked_by_user_id)"). - Set("impersonator_user_id = VALUES(impersonator_user_id)"). - Set("impersonation_reason = VALUES(impersonation_reason)"). - Set("impersonation_expires_at = VALUES(impersonation_expires_at)"). - Set("updated_at = ?", now). - Exec(ctx) - case *pgdialect.Dialect: - // PostgreSQL uses ON CONFLICT with EXCLUDED - _, err = r.db.NewInsert(). - Model(state). - On("CONFLICT (session_id) DO UPDATE"). - Set("revoked_at = EXCLUDED.revoked_at"). - Set("revoked_reason = EXCLUDED.revoked_reason"). - Set("revoked_by_user_id = EXCLUDED.revoked_by_user_id"). - Set("impersonator_user_id = EXCLUDED.impersonator_user_id"). - Set("impersonation_reason = EXCLUDED.impersonation_reason"). - Set("impersonation_expires_at = EXCLUDED.impersonation_expires_at"). - Set("updated_at = ?", now). - Exec(ctx) - case *sqlitedialect.Dialect: - // SQLite uses ON CONFLICT with excluded (lowercase) - _, err = r.db.NewInsert(). - Model(state). - On("CONFLICT (session_id) DO UPDATE"). - Set("revoked_at = excluded.revoked_at"). - Set("revoked_reason = excluded.revoked_reason"). - Set("revoked_by_user_id = excluded.revoked_by_user_id"). - Set("impersonator_user_id = excluded.impersonator_user_id"). - Set("impersonation_reason = excluded.impersonation_reason"). - Set("impersonation_expires_at = excluded.impersonation_expires_at"). - Set("updated_at = ?", now). - Exec(ctx) - default: - // Fallback for unknown dialects - try PostgreSQL syntax - _, err = r.db.NewInsert(). - Model(state). - On("CONFLICT (session_id) DO UPDATE"). - Set("revoked_at = EXCLUDED.revoked_at"). - Set("revoked_reason = EXCLUDED.revoked_reason"). - Set("revoked_by_user_id = EXCLUDED.revoked_by_user_id"). - Set("impersonator_user_id = EXCLUDED.impersonator_user_id"). - Set("impersonation_reason = EXCLUDED.impersonation_reason"). - Set("impersonation_expires_at = EXCLUDED.impersonation_expires_at"). - Set("updated_at = ?", now). - Exec(ctx) - } + _, err := r.db.NewInsert(). + Model(state). + On("CONFLICT (session_id) DO UPDATE"). + Set("revoked_at = EXCLUDED.revoked_at"). + Set("revoked_reason = EXCLUDED.revoked_reason"). + Set("revoked_by_user_id = EXCLUDED.revoked_by_user_id"). + Set("impersonator_user_id = EXCLUDED.impersonator_user_id"). + Set("impersonation_reason = EXCLUDED.impersonation_reason"). + Set("impersonation_expires_at = EXCLUDED.impersonation_expires_at"). + Set("updated_at = ?", now). + Exec(ctx) if err != nil { return fmt.Errorf("failed to upsert session state: %w", err) diff --git a/plugins/admin/repositories/session_state_repository_test.go b/plugins/admin/repositories/session_state_repository_test.go index c5ae2b29..ec16f371 100644 --- a/plugins/admin/repositories/session_state_repository_test.go +++ b/plugins/admin/repositories/session_state_repository_test.go @@ -15,7 +15,7 @@ import ( func setupSessionRepo(t *testing.T) (*repositories.BunSessionStateRepository, *bun.DB, func()) { t.Helper() - db, _ := tests.NewIntegrationTestDBFromEnv(t) + db := tests.NewIntegrationTestDB(t) ctx := context.Background() if _, err := db.NewCreateTable().Model((*types.AdminSessionState)(nil)).IfNotExists().Exec(ctx); err != nil { diff --git a/plugins/admin/repositories/user_state_repository_test.go b/plugins/admin/repositories/user_state_repository_test.go index 5fe86a62..bb22b39f 100644 --- a/plugins/admin/repositories/user_state_repository_test.go +++ b/plugins/admin/repositories/user_state_repository_test.go @@ -2,25 +2,17 @@ package repositories_test import ( "context" - "database/sql" "testing" + "time" - _ "github.com/mattn/go-sqlite3" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/sqlitedialect" - + "github.com/Authula/authula/internal/tests" "github.com/Authula/authula/plugins/admin/repositories" - "github.com/Authula/authula/plugins/admin/tests" "github.com/Authula/authula/plugins/admin/types" ) func setupRepo(t *testing.T) (*repositories.BunUserStateRepository, func()) { t.Helper() - sqldb, err := sql.Open("sqlite3", ":memory:") - if err != nil { - t.Fatalf("failed to open sqlite: %v", err) - } - db := bun.NewDB(sqldb, sqlitedialect.New()) + db := tests.NewIntegrationTestDB(t) ctx := context.Background() if _, err := db.NewCreateTable().Model((*types.AdminUserState)(nil)).IfNotExists().Exec(ctx); err != nil { @@ -29,11 +21,7 @@ func setupRepo(t *testing.T) (*repositories.BunUserStateRepository, func()) { repo := repositories.NewBunUserStateRepository(db) - cleanup := func() { - db.Close() - sqldb.Close() - } - return repo, cleanup + return repo, func() {} } func TestBunUserStateRepository_GetByUserID_NotFound(t *testing.T) { @@ -55,12 +43,13 @@ func TestBunUserStateRepository_UpsertAndRetrieve(t *testing.T) { defer cleanup() ctx := context.Background() + now := time.Now().UTC() state := &types.AdminUserState{ UserID: "u1", Banned: true, - BannedAt: tests.PtrTime(t, 0), - BannedReason: tests.PtrString(t, "reason"), - BannedByUserID: tests.PtrString(t, "actor"), + BannedAt: &now, + BannedReason: tests.PtrString("reason"), + BannedByUserID: tests.PtrString("actor"), } if err := repo.Upsert(ctx, state); err != nil { diff --git a/plugins/api-key/migrations.go b/plugins/api-key/migrations.go index d53ffb8b..1d0eda67 100644 --- a/plugins/api-key/migrations.go +++ b/plugins/api-key/migrations.go @@ -5,6 +5,6 @@ import ( "github.com/Authula/authula/plugins/api-key/migrationset" ) -func apiKeyMigrationsForProvider(provider string) []migrations.Migration { - return migrationset.ForProvider(provider) +func apiKeyMigrations() []migrations.Migration { + return migrationset.Migrations() } diff --git a/plugins/api-key/migrationset/migrations.go b/plugins/api-key/migrationset/migrations.go index ce331fd8..31676337 100644 --- a/plugins/api-key/migrationset/migrations.go +++ b/plugins/api-key/migrationset/migrations.go @@ -8,63 +8,8 @@ import ( "github.com/Authula/authula/migrations" ) -func ForProvider(provider string) []migrations.Migration { - return migrations.ForProvider(provider, migrations.ProviderVariants{ - "sqlite": func() []migrations.Migration { return []migrations.Migration{sqliteInitial()} }, - "postgres": func() []migrations.Migration { return []migrations.Migration{postgresInitial()} }, - "mysql": func() []migrations.Migration { return []migrations.Migration{mysqlInitial()} }, - }) -} - -func sqliteInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260425000000_api_keys_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `PRAGMA foreign_keys = ON;`, - // ------------------------------- - `CREATE TABLE IF NOT EXISTS api_keys ( - id VARCHAR(255) NOT NULL PRIMARY KEY, - key_hash VARCHAR(255) NOT NULL, - name VARCHAR(255) NOT NULL, - owner_type VARCHAR(255) NOT NULL, - owner_id VARCHAR(255) NOT NULL, - prefix VARCHAR(50), - start VARCHAR(10) NOT NULL, - last VARCHAR(10) NOT NULL, - enabled BOOLEAN NOT NULL DEFAULT 1, - rate_limit_enabled BOOLEAN NOT NULL DEFAULT 0, - last_requested_at TIMESTAMP, - expires_at TIMESTAMP, - permissions TEXT, - metadata TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP - );`, - `CREATE INDEX IF NOT EXISTS idx_api_keys_key_hash ON api_keys(key_hash);`, - `CREATE INDEX IF NOT EXISTS idx_api_keys_owner_id ON api_keys(owner_id);`, - `CREATE INDEX IF NOT EXISTS idx_api_keys_owner_type_owner_id ON api_keys(owner_type, owner_id);`, - `CREATE INDEX IF NOT EXISTS idx_api_keys_expires_at ON api_keys(expires_at);`, - `CREATE INDEX IF NOT EXISTS idx_api_keys_enabled ON api_keys(enabled);`, - `DROP TRIGGER IF EXISTS api_keys_updated_at_trigger;`, - `CREATE TRIGGER api_keys_updated_at_trigger - AFTER UPDATE ON api_keys - BEGIN - UPDATE api_keys SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; - END;`, - // ------------------------------- - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS api_keys;`, - ) - }, - } +func Migrations() []migrations.Migration { + return []migrations.Migration{postgresInitial()} } func postgresInitial() migrations.Migration { @@ -81,7 +26,6 @@ func postgresInitial() migrations.Migration { RETURN NEW; END; $$ LANGUAGE plpgsql;`, - // ------------------------------- `CREATE TABLE IF NOT EXISTS api_keys ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), key_hash VARCHAR(255) NOT NULL, @@ -124,46 +68,3 @@ func postgresInitial() migrations.Migration { }, } } - -func mysqlInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260425000000_api_keys_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS api_keys ( - id BINARY(16) NOT NULL PRIMARY KEY, - key_hash VARCHAR(255) NOT NULL, - name VARCHAR(255) NOT NULL, - owner_type VARCHAR(255) NOT NULL, - owner_id BINARY(16) NOT NULL, - prefix VARCHAR(50) NULL, - start VARCHAR(10) NOT NULL, - last VARCHAR(10) NOT NULL, - enabled TINYINT(1) NOT NULL DEFAULT 1, - rate_limit_enabled TINYINT(1) NOT NULL DEFAULT 0, - last_requested_at TIMESTAMP NULL, - expires_at TIMESTAMP NULL, - permissions TEXT NULL, - metadata JSON NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT chk_api_keys_owner_type CHECK (owner_type IN ('user', 'organization')), - UNIQUE KEY idx_api_keys_key_hash (key_hash), - INDEX idx_api_keys_owner_id (owner_id), - INDEX idx_api_keys_owner_type_owner_id (owner_type, owner_id), - INDEX idx_api_keys_expires_at (expires_at), - INDEX idx_api_keys_enabled (enabled) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS api_keys;`, - ) - }, - } -} diff --git a/plugins/api-key/plugin.go b/plugins/api-key/plugin.go index dcbfeb99..40223d97 100644 --- a/plugins/api-key/plugin.go +++ b/plugins/api-key/plugin.go @@ -113,8 +113,8 @@ func (p *ApiKeyPlugin) Init(ctx *models.PluginContext) error { return nil } -func (p *ApiKeyPlugin) Migrations(provider string) []migrations.Migration { - return apiKeyMigrationsForProvider(provider) +func (p *ApiKeyPlugin) Migrations() []migrations.Migration { + return apiKeyMigrations() } func (p *ApiKeyPlugin) DependsOn() []string { diff --git a/plugins/api-key/repositories/bun_api_key_repository_test.go b/plugins/api-key/repositories/bun_api_key_repository_test.go index b65f385c..bd8d24bd 100644 --- a/plugins/api-key/repositories/bun_api_key_repository_test.go +++ b/plugins/api-key/repositories/bun_api_key_repository_test.go @@ -2,31 +2,19 @@ package repositories import ( "context" - "database/sql" - "fmt" - "strings" "testing" "time" - _ "github.com/mattn/go-sqlite3" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/sqlitedialect" + "github.com/Authula/authula/internal/tests" "github.com/Authula/authula/plugins/api-key/types" ) func newTestApiKeyDB(t *testing.T) *bun.DB { t.Helper() - dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", strings.ReplaceAll(t.Name(), "/", "_")) - sqlDB, err := sql.Open("sqlite3", dsn) - if err != nil { - t.Fatalf("failed to open sqlite db: %v", err) - } - sqlDB.SetMaxOpenConns(1) - - db := bun.NewDB(sqlDB, sqlitedialect.New()) - t.Cleanup(func() { _ = db.Close() }) + db := tests.NewIntegrationTestDB(t) ctx := context.Background() if _, err := db.NewCreateTable().Model((*types.ApiKey)(nil)).IfNotExists().Exec(ctx); err != nil { diff --git a/plugins/jwt/migrations.go b/plugins/jwt/migrations.go index 90c89a10..e8c3c9be 100644 --- a/plugins/jwt/migrations.go +++ b/plugins/jwt/migrations.go @@ -5,6 +5,6 @@ import ( "github.com/Authula/authula/plugins/jwt/migrationset" ) -func JWTMigrationsForProvider(provider string) []migrations.Migration { - return migrationset.JWTMigrationsForProvider(provider) +func JWTMigrations() []migrations.Migration { + return migrationset.Migrations() } diff --git a/plugins/jwt/migrationset/migrations.go b/plugins/jwt/migrationset/migrations.go index 46cc823e..86c2a567 100644 --- a/plugins/jwt/migrationset/migrations.go +++ b/plugins/jwt/migrationset/migrations.go @@ -8,54 +8,8 @@ import ( "github.com/Authula/authula/migrations" ) -func JWTMigrationsForProvider(provider string) []migrations.Migration { - return migrations.ForProvider(provider, migrations.ProviderVariants{ - "sqlite": func() []migrations.Migration { return []migrations.Migration{jwtSQLiteInitial()} }, - "postgres": func() []migrations.Migration { return []migrations.Migration{jwtPostgresInitial()} }, - "mysql": func() []migrations.Migration { return []migrations.Migration{jwtMySQLInitial()} }, - }) -} - -func jwtSQLiteInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260131000000_jwt_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS jwks ( - id TEXT PRIMARY KEY, - public_key TEXT NOT NULL, - private_key TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP NULL - );`, - `CREATE INDEX IF NOT EXISTS idx_jwks_expires_at ON jwks(expires_at);`, - `CREATE TABLE IF NOT EXISTS refresh_tokens ( - id TEXT PRIMARY KEY, - session_id TEXT NOT NULL, - token_hash TEXT NOT NULL UNIQUE, - expires_at TIMESTAMP NOT NULL, - is_revoked INTEGER DEFAULT 0, - revoked_at TIMESTAMP NULL, - last_reuse_attempt TIMESTAMP NULL DEFAULT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - );`, - `CREATE INDEX IF NOT EXISTS idx_refresh_tokens_session_id ON refresh_tokens(session_id);`, - `CREATE INDEX IF NOT EXISTS idx_refresh_tokens_token_hash ON refresh_tokens(token_hash);`, - `CREATE INDEX IF NOT EXISTS idx_refresh_tokens_expires_at ON refresh_tokens(expires_at);`, - `CREATE INDEX IF NOT EXISTS idx_refresh_tokens_revoked_only ON refresh_tokens(is_revoked) WHERE is_revoked = 1;`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS refresh_tokens;`, - `DROP TABLE IF EXISTS jwks;`, - ) - }, - } +func Migrations() []migrations.Migration { + return []migrations.Migration{jwtPostgresInitial()} } func jwtPostgresInitial() migrations.Migration { @@ -106,52 +60,3 @@ func jwtPostgresInitial() migrations.Migration { }, } } - -func jwtMySQLInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260131000000_jwt_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS jwks ( - id BINARY(16) NOT NULL PRIMARY KEY, - public_key TEXT NOT NULL, - private_key TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP NULL - );`, - `CREATE INDEX idx_jwks_expires_at ON jwks(expires_at);`, - `CREATE TABLE IF NOT EXISTS refresh_tokens ( - id BINARY(16) NOT NULL PRIMARY KEY, - session_id BINARY(16) NOT NULL, - token_hash VARCHAR(64) UNIQUE NOT NULL, - expires_at TIMESTAMP NOT NULL, - is_revoked BOOLEAN DEFAULT FALSE, - revoked_at TIMESTAMP NULL, - last_reuse_attempt TIMESTAMP NULL DEFAULT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT fk_refresh_tokens_session FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE - );`, - `CREATE INDEX idx_refresh_tokens_session_id ON refresh_tokens(session_id);`, - `CREATE INDEX idx_refresh_tokens_expires_at ON refresh_tokens(expires_at);`, - `CREATE INDEX idx_refresh_tokens_active_session ON refresh_tokens(session_id, is_revoked);`, - `CREATE INDEX idx_refresh_tokens_last_reuse_attempt ON refresh_tokens(last_reuse_attempt);`, - `DROP PROCEDURE IF EXISTS cleanup_expired_refresh_tokens;`, - `CREATE PROCEDURE cleanup_expired_refresh_tokens() - BEGIN - DELETE FROM refresh_tokens WHERE expires_at < NOW(); - END;`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP PROCEDURE IF EXISTS cleanup_expired_refresh_tokens;`, - `DROP TABLE IF EXISTS refresh_tokens;`, - `DROP TABLE IF EXISTS jwks;`, - ) - }, - } -} diff --git a/plugins/jwt/plugin.go b/plugins/jwt/plugin.go index b4ec171e..f2890c3b 100644 --- a/plugins/jwt/plugin.go +++ b/plugins/jwt/plugin.go @@ -134,8 +134,8 @@ func (p *JWTPlugin) Init(ctx *models.PluginContext) error { return nil } -func (p *JWTPlugin) Migrations(provider string) []migrations.Migration { - return JWTMigrationsForProvider(provider) +func (p *JWTPlugin) Migrations() []migrations.Migration { + return JWTMigrations() } func (p *JWTPlugin) DependsOn() []string { diff --git a/plugins/jwt/repositories/jwks_repository_test.go b/plugins/jwt/repositories/jwks_repository_test.go index ce859597..758edc98 100644 --- a/plugins/jwt/repositories/jwks_repository_test.go +++ b/plugins/jwt/repositories/jwks_repository_test.go @@ -11,21 +11,27 @@ import ( internaltests "github.com/Authula/authula/internal/tests" "github.com/Authula/authula/migrations" + "github.com/Authula/authula/models" "github.com/Authula/authula/plugins/jwt/migrationset" "github.com/Authula/authula/plugins/jwt/types" ) func setupJWKSRepo(t *testing.T) (*bun.DB, *bunJWKSRepository) { t.Helper() - db := internaltests.NewSQLiteIntegrationDB(t) + db := internaltests.NewIntegrationTestDB(t) migrator, err := migrations.NewMigrator(db, &internaltests.MockLogger{}) require.NoError(t, err) - err = migrator.Migrate(context.Background(), []migrations.MigrationSet{ - { - PluginID: "jwt", - Migrations: migrationset.JWTMigrationsForProvider("sqlite"), - }, - }) + + coreSet, err := migrations.CoreMigrationSet() + require.NoError(t, err) + + jwtSet := migrations.MigrationSet{ + PluginID: models.PluginJWT.String(), + DependsOn: []string{migrations.CorePluginID}, + Migrations: migrationset.Migrations(), + } + + err = migrator.Migrate(context.Background(), []migrations.MigrationSet{coreSet, jwtSet}) require.NoError(t, err) return db, &bunJWKSRepository{db: db} } @@ -78,7 +84,7 @@ func TestBunJWKSRepository(t *testing.T) { { name: "GetJWKSKeyByID_not_found", run: func(t *testing.T, repo *bunJWKSRepository, ctx context.Context) { - found, err := repo.GetJWKSKeyByID(ctx, "non-existent-id") + found, err := repo.GetJWKSKeyByID(ctx, "00000000-0000-0000-0000-000000000000") require.NoError(t, err) require.Nil(t, found) }, diff --git a/plugins/jwt/repositories/refresh_token_repository_test.go b/plugins/jwt/repositories/refresh_token_repository_test.go index c72ceaff..65b1cf7e 100644 --- a/plugins/jwt/repositories/refresh_token_repository_test.go +++ b/plugins/jwt/repositories/refresh_token_repository_test.go @@ -11,21 +11,48 @@ import ( internaltests "github.com/Authula/authula/internal/tests" "github.com/Authula/authula/migrations" + "github.com/Authula/authula/models" "github.com/Authula/authula/plugins/jwt/migrationset" "github.com/Authula/authula/plugins/jwt/types" ) +func seedSession(t *testing.T, ctx context.Context, db bun.IDB, sessionID string) { + t.Helper() + + user := &models.User{ + ID: sessionID, + Name: "test-" + sessionID, + Email: sessionID + "@test.com", + } + _, err := db.NewInsert().Model(user).Exec(ctx) + require.NoError(t, err) + + sess := &models.Session{ + ID: sessionID, + UserID: sessionID, + Token: "token-" + sessionID, + ExpiresAt: time.Now().UTC().Add(time.Hour), + } + _, err = db.NewInsert().Model(sess).Exec(ctx) + require.NoError(t, err) +} + func setupRefreshTokenRepo(t *testing.T) (*bun.DB, *refreshTokenRepositoryImpl) { t.Helper() - db := internaltests.NewSQLiteIntegrationDB(t) + db := internaltests.NewIntegrationTestDB(t) migrator, err := migrations.NewMigrator(db, &internaltests.MockLogger{}) require.NoError(t, err) - err = migrator.Migrate(context.Background(), []migrations.MigrationSet{ - { - PluginID: "jwt", - Migrations: migrationset.JWTMigrationsForProvider("sqlite"), - }, - }) + + coreSet, err := migrations.CoreMigrationSet() + require.NoError(t, err) + + jwtSet := migrations.MigrationSet{ + PluginID: models.PluginJWT.String(), + DependsOn: []string{migrations.CorePluginID}, + Migrations: migrationset.Migrations(), + } + + err = migrator.Migrate(context.Background(), []migrations.MigrationSet{coreSet, jwtSet}) require.NoError(t, err) return db, &refreshTokenRepositoryImpl{db: db} } @@ -60,6 +87,8 @@ func TestRefreshTokenRepository_Store(t *testing.T) { record := tt.record() now := time.Now().Truncate(time.Millisecond) + seedSession(t, ctx, repo.db, record.SessionID) + err := repo.StoreRefreshToken(ctx, record) require.NoError(t, err) @@ -106,9 +135,11 @@ func TestRefreshTokenRepository_GetRefreshToken(t *testing.T) { ctx := context.Background() if !tt.wantNil { + sessionID := uuid.New().String() + seedSession(t, ctx, repo.db, sessionID) err := repo.StoreRefreshToken(ctx, &types.RefreshToken{ ID: uuid.New().String(), - SessionID: uuid.New().String(), + SessionID: sessionID, TokenHash: tt.tokenHash, ExpiresAt: time.Now().Add(time.Hour), }) @@ -153,6 +184,8 @@ func TestRefreshTokenRepository_RevokeRefreshToken(t *testing.T) { _, repo := setupRefreshTokenRepo(t) ctx := context.Background() + seedSession(t, ctx, repo.db, tt.record.SessionID) + err := repo.StoreRefreshToken(ctx, tt.record) require.NoError(t, err) @@ -209,6 +242,9 @@ func TestRefreshTokenRepository_RevokeAllSessionTokens(t *testing.T) { ExpiresAt: time.Now().Add(time.Hour), } + seedSession(t, ctx, repo.db, tt.targetSession) + seedSession(t, ctx, repo.db, tt.otherSession) + require.NoError(t, repo.StoreRefreshToken(ctx, token1)) require.NoError(t, repo.StoreRefreshToken(ctx, token2)) require.NoError(t, repo.StoreRefreshToken(ctx, token3)) @@ -260,6 +296,8 @@ func TestRefreshTokenRepository_SetLastReuseAttempt(t *testing.T) { _, repo := setupRefreshTokenRepo(t) ctx := context.Background() + seedSession(t, ctx, repo.db, tt.record.SessionID) + err := repo.StoreRefreshToken(ctx, tt.record) require.NoError(t, err) @@ -306,6 +344,9 @@ func TestRefreshTokenRepository_CleanupExpiredTokens(t *testing.T) { _, repo := setupRefreshTokenRepo(t) ctx := context.Background() + seedSession(t, ctx, repo.db, tt.expiredRecord.SessionID) + seedSession(t, ctx, repo.db, tt.validRecord.SessionID) + require.NoError(t, repo.StoreRefreshToken(ctx, tt.expiredRecord)) require.NoError(t, repo.StoreRefreshToken(ctx, tt.validRecord)) diff --git a/plugins/jwt/services/cache_service_test.go b/plugins/jwt/services/cache_service_test.go index 790d18db..7401d30a 100644 --- a/plugins/jwt/services/cache_service_test.go +++ b/plugins/jwt/services/cache_service_test.go @@ -32,16 +32,20 @@ type cacheTestFixture struct { func newCacheTestFixture(t *testing.T) *cacheTestFixture { t.Helper() - db := internaltests.NewSQLiteIntegrationDB(t) - + db := internaltests.NewIntegrationTestDB(t) migrator, err := migrations.NewMigrator(db, &internaltests.MockLogger{}) require.NoError(t, err) - err = migrator.Migrate(context.Background(), []migrations.MigrationSet{ - { - PluginID: models.PluginJWT.String(), - Migrations: migrationset.JWTMigrationsForProvider("sqlite"), - }, - }) + + coreSet, err := migrations.CoreMigrationSet() + require.NoError(t, err) + + jwtSet := migrations.MigrationSet{ + PluginID: models.PluginJWT.String(), + DependsOn: []string{migrations.CorePluginID}, + Migrations: migrationset.Migrations(), + } + + err = migrator.Migrate(context.Background(), []migrations.MigrationSet{coreSet, jwtSet}) require.NoError(t, err) return &cacheTestFixture{ diff --git a/plugins/jwt/services/key_service_test.go b/plugins/jwt/services/key_service_test.go index b35d5387..6823f88f 100644 --- a/plugins/jwt/services/key_service_test.go +++ b/plugins/jwt/services/key_service_test.go @@ -21,16 +21,20 @@ import ( func setupKeyServiceTest(t *testing.T) (KeyService, repositories.JWKSRepository) { t.Helper() - db := internaltests.NewSQLiteIntegrationDB(t) - + db := internaltests.NewIntegrationTestDB(t) migrator, err := migrations.NewMigrator(db, &internaltests.MockLogger{}) require.NoError(t, err) - err = migrator.Migrate(context.Background(), []migrations.MigrationSet{ - { - PluginID: models.PluginJWT.String(), - Migrations: migrationset.JWTMigrationsForProvider("sqlite"), - }, - }) + + coreSet, err := migrations.CoreMigrationSet() + require.NoError(t, err) + + jwtSet := migrations.MigrationSet{ + PluginID: models.PluginJWT.String(), + DependsOn: []string{migrations.CorePluginID}, + Migrations: migrationset.Migrations(), + } + + err = migrator.Migrate(context.Background(), []migrations.MigrationSet{coreSet, jwtSet}) require.NoError(t, err) repo := repositories.NewBunJWKSRepository(db) @@ -58,7 +62,7 @@ func TestKeyService_GenerateKeysIfMissing(t *testing.T) { name: "keys exist", setup: func(ctx context.Context, repo repositories.JWKSRepository) { err := repo.StoreJWKSKey(ctx, &types.JWKS{ - ID: "pre-seeded-key", + ID: "00000000-0000-0000-0000-000000000001", PublicKey: "pre-seeded-public-key", PrivateKey: "pre-seeded-private-key", }) @@ -90,7 +94,7 @@ func TestKeyService_GenerateKeysIfMissing(t *testing.T) { assert.Contains(t, keys[0].PrivateKey, "BEGIN PRIVATE KEY") } else { require.Len(t, keys, 1) - require.Equal(t, "pre-seeded-key", keys[0].ID) + require.Equal(t, "00000000-0000-0000-0000-000000000001", keys[0].ID) } }) } @@ -114,20 +118,20 @@ func TestKeyService_GetActiveKey(t *testing.T) { name: "single key", setup: func(ctx context.Context, repo repositories.JWKSRepository) { err := repo.StoreJWKSKey(ctx, &types.JWKS{ - ID: "single-key", + ID: "00000000-0000-0000-0000-000000000001", PublicKey: "public-key-1", PrivateKey: "private-key-1", CreatedAt: time.Now(), }) require.NoError(t, err) }, - wantID: "single-key", + wantID: "00000000-0000-0000-0000-000000000001", }, { name: "returns most recent key", setup: func(ctx context.Context, repo repositories.JWKSRepository) { err := repo.StoreJWKSKey(ctx, &types.JWKS{ - ID: "old-key", + ID: "00000000-0000-0000-0000-000000000002", PublicKey: "public-key-old", PrivateKey: "private-key-old", CreatedAt: time.Now().Add(-1 * time.Hour), @@ -135,14 +139,14 @@ func TestKeyService_GetActiveKey(t *testing.T) { require.NoError(t, err) err = repo.StoreJWKSKey(ctx, &types.JWKS{ - ID: "new-key", + ID: "00000000-0000-0000-0000-000000000003", PublicKey: "public-key-new", PrivateKey: "private-key-new", CreatedAt: time.Now(), }) require.NoError(t, err) }, - wantID: "new-key", + wantID: "00000000-0000-0000-0000-000000000003", }, } @@ -186,7 +190,7 @@ func TestKeyService_RotateKeysIfNeeded(t *testing.T) { name: "rotation due", setup: func(ctx context.Context, repo repositories.JWKSRepository) { err := repo.StoreJWKSKey(ctx, &types.JWKS{ - ID: "old-key", + ID: "00000000-0000-0000-0000-000000000002", PublicKey: "old-public-key", PrivateKey: "old-private-key", CreatedAt: time.Now().Add(-25 * time.Hour), @@ -199,7 +203,7 @@ func TestKeyService_RotateKeysIfNeeded(t *testing.T) { name: "not due", setup: func(ctx context.Context, repo repositories.JWKSRepository) { err := repo.StoreJWKSKey(ctx, &types.JWKS{ - ID: "recent-key", + ID: "00000000-0000-0000-0000-000000000004", PublicKey: "recent-public-key", PrivateKey: "recent-private-key", CreatedAt: time.Now().Add(-1 * time.Hour), @@ -234,7 +238,7 @@ func TestKeyService_RotateKeysIfNeeded(t *testing.T) { if tt.wantRotated { require.Len(t, keys, 2) // Old key should have expires_at set - oldKey, err := repo.GetJWKSKeyByID(ctx, "old-key") + oldKey, err := repo.GetJWKSKeyByID(ctx, "00000000-0000-0000-0000-000000000002") require.NoError(t, err) require.NotNil(t, oldKey) require.NotNil(t, oldKey.ExpiresAt) diff --git a/plugins/organizations/migrations.go b/plugins/organizations/migrations.go index 670b880c..b82412ba 100644 --- a/plugins/organizations/migrations.go +++ b/plugins/organizations/migrations.go @@ -5,6 +5,6 @@ import ( "github.com/Authula/authula/plugins/organizations/migrationset" ) -func organizationsMigrationsForProvider(provider string) []migrations.Migration { - return migrationset.ForProvider(provider) +func organizationsMigrations() []migrations.Migration { + return migrationset.Migrations() } diff --git a/plugins/organizations/migrationset/migrations.go b/plugins/organizations/migrationset/migrations.go index ca181793..5c377392 100644 --- a/plugins/organizations/migrationset/migrations.go +++ b/plugins/organizations/migrationset/migrations.go @@ -8,131 +8,11 @@ import ( "github.com/Authula/authula/migrations" ) -func ForProvider(provider string) []migrations.Migration { - return migrations.ForProvider(provider, migrations.ProviderVariants{ - "sqlite": func() []migrations.Migration { return []migrations.Migration{organizationsSQLiteInitial()} }, - "postgres": func() []migrations.Migration { return []migrations.Migration{organizationsPostgresInitial()} }, - "mysql": func() []migrations.Migration { return []migrations.Migration{organizationsMySQLInitial()} }, - }) +func Migrations() []migrations.Migration { + return []migrations.Migration{postgresInitial()} } -func organizationsSQLiteInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260409000000_organizations_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `PRAGMA foreign_keys = ON;`, - // ----------------------------------- - `CREATE TABLE IF NOT EXISTS organizations ( - id TEXT PRIMARY KEY, - owner_id TEXT NOT NULL, - name VARCHAR(255) NOT NULL, - slug VARCHAR(255) NOT NULL UNIQUE, - logo TEXT, - metadata TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE - );`, - `CREATE INDEX IF NOT EXISTS idx_organizations_owner_id ON organizations(owner_id);`, - `DROP TRIGGER IF EXISTS update_organizations_updated_at_trigger;`, - `CREATE TRIGGER update_organizations_updated_at_trigger - AFTER UPDATE ON organizations - FOR EACH ROW - WHEN NEW.updated_at <= OLD.updated_at - BEGIN - UPDATE organizations SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; - END;`, - // ----------------------------------- - `CREATE TABLE IF NOT EXISTS organization_invitations ( - id TEXT PRIMARY KEY, - email VARCHAR(255) NOT NULL, - inviter_id TEXT NOT NULL, - organization_id TEXT NOT NULL, - role VARCHAR(255) NOT NULL, - status VARCHAR(32) NOT NULL DEFAULT 'pending', - expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, - FOREIGN KEY (inviter_id) REFERENCES users(id) ON DELETE CASCADE - );`, - `CREATE INDEX IF NOT EXISTS idx_organization_invitations_email ON organization_invitations(email);`, - `CREATE INDEX IF NOT EXISTS idx_organization_invitations_organization_id ON organization_invitations(organization_id);`, - `CREATE INDEX IF NOT EXISTS idx_organization_invitations_inviter_id ON organization_invitations(inviter_id);`, - `CREATE INDEX IF NOT EXISTS idx_organization_invitations_status_expires_at ON organization_invitations(status, expires_at);`, - // ----------------------------------- - `CREATE TABLE IF NOT EXISTS organization_members ( - id TEXT PRIMARY KEY, - organization_id TEXT NOT NULL, - user_id TEXT NOT NULL, - role VARCHAR(255) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - UNIQUE (organization_id, user_id) - );`, - `CREATE INDEX IF NOT EXISTS idx_organization_members_organization_id ON organization_members(organization_id);`, - `CREATE INDEX IF NOT EXISTS idx_organization_members_user_id ON organization_members(user_id);`, - `DROP TRIGGER IF EXISTS update_organization_members_updated_at_trigger;`, - `CREATE TRIGGER update_organization_members_updated_at_trigger - AFTER UPDATE ON organization_members - BEGIN - UPDATE organization_members SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; - END;`, - // ----------------------------------- - `CREATE TABLE IF NOT EXISTS organization_teams ( - id TEXT PRIMARY KEY, - organization_id TEXT NOT NULL, - name VARCHAR(255) NOT NULL UNIQUE, - slug VARCHAR(255) NOT NULL UNIQUE, - description TEXT, - metadata TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, - UNIQUE (organization_id, slug) - );`, - `CREATE INDEX IF NOT EXISTS idx_organization_teams_organization_id ON organization_teams(organization_id);`, - `CREATE INDEX IF NOT EXISTS idx_organization_teams_slug ON organization_teams(slug);`, - `DROP TRIGGER IF EXISTS update_organization_teams_updated_at_trigger;`, - `CREATE TRIGGER update_organization_teams_updated_at_trigger - AFTER UPDATE ON organization_teams - BEGIN - UPDATE organization_teams SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; - END;`, - // ----------------------------------- - `CREATE TABLE IF NOT EXISTS organization_team_members ( - id TEXT PRIMARY KEY, - team_id TEXT NOT NULL, - member_id TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (team_id) REFERENCES organization_teams(id) ON DELETE CASCADE, - FOREIGN KEY (member_id) REFERENCES organization_members(id) ON DELETE CASCADE, - UNIQUE (team_id, member_id) - );`, - `CREATE INDEX IF NOT EXISTS idx_organization_team_members_team_id ON organization_team_members(team_id);`, - `CREATE INDEX IF NOT EXISTS idx_organization_team_members_member_id ON organization_team_members(member_id);`, - // ----------------------------------- - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS organization_team_members;`, - `DROP TABLE IF EXISTS organization_teams;`, - `DROP TABLE IF EXISTS organization_members;`, - `DROP TABLE IF EXISTS organization_invitations;`, - `DROP TABLE IF EXISTS organizations;`, - ) - }, - } -} - -func organizationsPostgresInitial() migrations.Migration { +func postgresInitial() migrations.Migration { return migrations.Migration{ Version: "20260409000000_organizations_initial", Up: func(ctx context.Context, tx bun.Tx) error { @@ -260,100 +140,3 @@ func organizationsPostgresInitial() migrations.Migration { }, } } - -func organizationsMySQLInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260409000000_organizations_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - // ----------------------------------- - `CREATE TABLE IF NOT EXISTS organizations ( - id BINARY(16) NOT NULL PRIMARY KEY, - owner_id BINARY(16) NOT NULL, - name VARCHAR(255) NOT NULL, - slug VARCHAR(255) NOT NULL UNIQUE, - logo TEXT NULL, - metadata JSON NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_organizations_owner FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE, - INDEX idx_organizations_owner_id (owner_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - // ----------------------------------- - `CREATE TABLE IF NOT EXISTS organization_invitations ( - id BINARY(16) NOT NULL PRIMARY KEY, - organization_id BINARY(16) NOT NULL, - inviter_id BINARY(16) NOT NULL, - email VARCHAR(255) NOT NULL, - role VARCHAR(255) NOT NULL, - status VARCHAR(32) NOT NULL DEFAULT 'pending', - expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_organization_invitations_organization FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, - CONSTRAINT fk_organization_invitations_inviter FOREIGN KEY (inviter_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT chk_organization_invitations_status CHECK (status IN ('pending', 'accepted', 'rejected', 'revoked', 'expired')), - INDEX idx_organization_invitations_organization_id (organization_id), - INDEX idx_organization_invitations_inviter_id (inviter_id), - INDEX idx_organization_invitations_email (email), - INDEX idx_organization_invitations_status_expires_at (status, expires_at) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - // ----------------------------------- - `CREATE TABLE IF NOT EXISTS organization_members ( - id BINARY(16) NOT NULL PRIMARY KEY, - organization_id BINARY(16) NOT NULL, - user_id BINARY(16) NOT NULL, - role VARCHAR(255) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_organization_members_organization FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, - CONSTRAINT fk_organization_members_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT uq_organization_members_organization_user UNIQUE (organization_id, user_id), - INDEX idx_organization_members_organization_id (organization_id), - INDEX idx_organization_members_user_id (user_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - // ----------------------------------- - `CREATE TABLE IF NOT EXISTS organization_teams ( - id BINARY(16) NOT NULL PRIMARY KEY, - organization_id BINARY(16) NOT NULL, - name VARCHAR(255) NOT NULL, - slug VARCHAR(255) NOT NULL, - description TEXT NULL, - metadata JSON NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_organization_teams_organization FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, - CONSTRAINT uq_organization_teams_organization_slug UNIQUE (organization_id, slug), - INDEX idx_organization_teams_organization_id (organization_id), - INDEX idx_organization_teams_slug (slug) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - // ----------------------------------- - `CREATE TABLE IF NOT EXISTS organization_team_members ( - id BINARY(16) NOT NULL PRIMARY KEY, - team_id BINARY(16) NOT NULL, - member_id BINARY(16) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT fk_organization_team_members_team FOREIGN KEY (team_id) REFERENCES organization_teams(id) ON DELETE CASCADE, - CONSTRAINT fk_organization_team_members_member FOREIGN KEY (member_id) REFERENCES organization_members(id) ON DELETE CASCADE, - CONSTRAINT uq_organization_team_members_team_member UNIQUE (team_id, member_id), - INDEX idx_organization_team_members_team_id (team_id), - INDEX idx_organization_team_members_member_id (member_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - // ----------------------------------- - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS organization_team_members;`, - `DROP TABLE IF EXISTS organization_teams;`, - `DROP TABLE IF EXISTS organization_members;`, - `DROP TABLE IF EXISTS organization_invitations;`, - `DROP TABLE IF EXISTS organizations;`, - ) - }, - } -} diff --git a/plugins/organizations/plugin.go b/plugins/organizations/plugin.go index 5d9c82ed..0add8f2d 100644 --- a/plugins/organizations/plugin.go +++ b/plugins/organizations/plugin.go @@ -114,8 +114,8 @@ func (p *OrganizationsPlugin) Init(ctx *models.PluginContext) error { return nil } -func (p *OrganizationsPlugin) Migrations(provider string) []migrations.Migration { - return organizationsMigrationsForProvider(provider) +func (p *OrganizationsPlugin) Migrations() []migrations.Migration { + return organizationsMigrations() } func (p *OrganizationsPlugin) DependsOn() []string { diff --git a/plugins/organizations/repositories/bun_organization_invitation_repository_test.go b/plugins/organizations/repositories/bun_organization_invitation_repository_test.go index 8ca0e628..a9f979c0 100644 --- a/plugins/organizations/repositories/bun_organization_invitation_repository_test.go +++ b/plugins/organizations/repositories/bun_organization_invitation_repository_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/uptrace/bun" @@ -16,6 +17,11 @@ import ( func TestBunOrganizationInvitationRepository_Create(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + inv1ID := uuid.New().String() + tests := []struct { name string invitation *types.OrganizationInvitation @@ -24,10 +30,10 @@ func TestBunOrganizationInvitationRepository_Create(t *testing.T) { { name: "pending", invitation: &types.OrganizationInvitation{ - ID: "inv-1", + ID: inv1ID, Email: "user@example.com", - InviterID: "user-1", - OrganizationID: "org-1", + InviterID: user1ID, + OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour), @@ -40,8 +46,8 @@ func TestBunOrganizationInvitationRepository_Create(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) created, err := repo.Create(context.Background(), tt.invitation) @@ -56,6 +62,13 @@ func TestBunOrganizationInvitationRepository_Create(t *testing.T) { func TestBunOrganizationInvitationRepository_GetByOrganizationIDAndEmail(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + inv1ID := uuid.New().String() + inv2ID := uuid.New().String() + inv3ID := uuid.New().String() + tests := []struct { name string organizationID string @@ -68,23 +81,23 @@ func TestBunOrganizationInvitationRepository_GetByOrganizationIDAndEmail(t *test }{ { name: "found latest invitation regardless of status", - organizationID: "org-1", + organizationID: org1ID, email: "user@example.com", expectFound: true, - expectID: "inv-3", + expectID: inv3ID, expectStatus: types.OrganizationInvitationStatusPending, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-2", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv2ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-3", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv3ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) return repo, ctx @@ -92,24 +105,24 @@ func TestBunOrganizationInvitationRepository_GetByOrganizationIDAndEmail(t *test }, { name: "found pending invitation when status is filtered", - organizationID: "org-1", + organizationID: org1ID, email: "user@example.com", status: []types.OrganizationInvitationStatus{types.OrganizationInvitationStatusPending}, expectFound: true, - expectID: "inv-3", + expectID: inv3ID, expectStatus: types.OrganizationInvitationStatusPending, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-2", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv2ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-3", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv3ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) return repo, ctx @@ -117,24 +130,24 @@ func TestBunOrganizationInvitationRepository_GetByOrganizationIDAndEmail(t *test }, { name: "found accepted invitation when status is filtered", - organizationID: "org-1", + organizationID: org1ID, email: "user@example.com", status: []types.OrganizationInvitationStatus{types.OrganizationInvitationStatusAccepted}, expectFound: true, - expectID: "inv-2", + expectID: inv2ID, expectStatus: types.OrganizationInvitationStatusAccepted, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-2", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv2ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-3", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv3ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) return repo, ctx @@ -142,24 +155,24 @@ func TestBunOrganizationInvitationRepository_GetByOrganizationIDAndEmail(t *test }, { name: "found latest invitation across multiple statuses", - organizationID: "org-1", + organizationID: org1ID, email: "user@example.com", status: []types.OrganizationInvitationStatus{types.OrganizationInvitationStatusAccepted, types.OrganizationInvitationStatusPending}, expectFound: true, - expectID: "inv-3", + expectID: inv3ID, expectStatus: types.OrganizationInvitationStatusPending, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-2", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv2ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-3", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv3ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) return repo, ctx @@ -167,12 +180,12 @@ func TestBunOrganizationInvitationRepository_GetByOrganizationIDAndEmail(t *test }, { name: "not found", - organizationID: "org-1", + organizationID: org1ID, email: "missing@example.com", setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") return repositories.NewBunOrganizationInvitationRepository(db), context.Background() }, }, @@ -200,6 +213,13 @@ func TestBunOrganizationInvitationRepository_GetByOrganizationIDAndEmail(t *test func TestBunOrganizationInvitationRepository_GetAllPendingByEmail(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + org2ID := uuid.New().String() + inv1ID := uuid.New().String() + inv2ID := uuid.New().String() + tests := []struct { name string email string @@ -212,15 +232,15 @@ func TestBunOrganizationInvitationRepository_GetAllPendingByEmail(t *testing.T) expectPending: 1, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganization(t, db, "org-2", "user-2", "Beta Inc", "beta-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganization(t, db, org2ID, user2ID, "Beta Inc", "beta-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-2", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-2", Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv2ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org2ID, Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) return repo, ctx @@ -232,9 +252,9 @@ func TestBunOrganizationInvitationRepository_GetAllPendingByEmail(t *testing.T) expectPending: 0, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganization(t, db, "org-2", "user-2", "Beta Inc", "beta-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganization(t, db, org2ID, user2ID, "Beta Inc", "beta-inc") return repositories.NewBunOrganizationInvitationRepository(db), context.Background() }, }, @@ -256,6 +276,13 @@ func TestBunOrganizationInvitationRepository_GetAllPendingByEmail(t *testing.T) func TestBunOrganizationInvitationRepository_GetAllByOrganizationID(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + org2ID := uuid.New().String() + inv1ID := uuid.New().String() + inv2ID := uuid.New().String() + tests := []struct { name string organizationID string @@ -265,19 +292,19 @@ func TestBunOrganizationInvitationRepository_GetAllByOrganizationID(t *testing.T }{ { name: "returns invitations for organization", - organizationID: "org-1", + organizationID: org1ID, expectCount: 2, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganization(t, db, "org-2", "user-2", "Beta Inc", "beta-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganization(t, db, org2ID, user2ID, "Beta Inc", "beta-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-2", Email: "other@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusRejected, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv2ID, Email: "other@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusRejected, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) return repo, ctx @@ -289,8 +316,8 @@ func TestBunOrganizationInvitationRepository_GetAllByOrganizationID(t *testing.T statusByID[invitation.ID] = invitation.Status } - require.Equal(t, types.OrganizationInvitationStatusPending, statusByID["inv-1"]) - require.Equal(t, types.OrganizationInvitationStatusRejected, statusByID["inv-2"]) + require.Equal(t, types.OrganizationInvitationStatusPending, statusByID[inv1ID]) + require.Equal(t, types.OrganizationInvitationStatusRejected, statusByID[inv2ID]) }, }, } @@ -313,6 +340,11 @@ func TestBunOrganizationInvitationRepository_GetAllByOrganizationID(t *testing.T func TestBunOrganizationInvitationRepository_GetByID(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + inv1ID := uuid.New().String() + tests := []struct { name string invitationID string @@ -321,16 +353,16 @@ func TestBunOrganizationInvitationRepository_GetByID(t *testing.T) { }{ { name: "found", - invitationID: "inv-1", + invitationID: inv1ID, expectFound: true, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) return repo, ctx @@ -338,11 +370,11 @@ func TestBunOrganizationInvitationRepository_GetByID(t *testing.T) { }, { name: "not found", - invitationID: "missing", + invitationID: "00000000-0000-0000-0000-000000000000", setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") return repositories.NewBunOrganizationInvitationRepository(db), context.Background() }, }, @@ -358,7 +390,7 @@ func TestBunOrganizationInvitationRepository_GetByID(t *testing.T) { require.NoError(t, err) if tt.expectFound { require.NotNil(t, found) - require.Equal(t, "inv-1", found.ID) + require.Equal(t, inv1ID, found.ID) return } require.Nil(t, found) @@ -369,6 +401,11 @@ func TestBunOrganizationInvitationRepository_GetByID(t *testing.T) { func TestBunOrganizationInvitationRepository_Update(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + inv1ID := uuid.New().String() + tests := []struct { name string setup func(*testing.T) (repositories.OrganizationInvitationRepository, context.Context, *types.OrganizationInvitation) @@ -377,12 +414,12 @@ func TestBunOrganizationInvitationRepository_Update(t *testing.T) { name: "updates invitation status", setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context, *types.OrganizationInvitation) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() - created, err := repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + created, err := repo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) return repo, ctx, created }, @@ -406,6 +443,14 @@ func TestBunOrganizationInvitationRepository_Update(t *testing.T) { func TestBunOrganizationInvitationRepository_CountByOrganizationIDAndEmail(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + org2ID := uuid.New().String() + inv1ID := uuid.New().String() + inv2ID := uuid.New().String() + inv3ID := uuid.New().String() + tests := []struct { name string organizationID string @@ -415,22 +460,22 @@ func TestBunOrganizationInvitationRepository_CountByOrganizationIDAndEmail(t *te }{ { name: "counts all invitations for org/email pair", - organizationID: "org-1", + organizationID: org1ID, email: "user@example.com", expectCount: 2, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganization(t, db, "org-2", "user-2", "Beta Inc", "beta-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganization(t, db, org2ID, user2ID, "Beta Inc", "beta-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-2", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv2ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-3", Email: "user@example.com", InviterID: "user-2", OrganizationID: "org-2", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv3ID, Email: "user@example.com", InviterID: user2ID, OrganizationID: org2ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) return repo, ctx @@ -438,22 +483,22 @@ func TestBunOrganizationInvitationRepository_CountByOrganizationIDAndEmail(t *te }, { name: "same email other org does not count", - organizationID: "org-2", + organizationID: org2ID, email: "user@example.com", expectCount: 1, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganization(t, db, "org-2", "user-2", "Beta Inc", "beta-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganization(t, db, org2ID, user2ID, "Beta Inc", "beta-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err := repo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-2", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv2ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusAccepted, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: "inv-3", Email: "user@example.com", InviterID: "user-2", OrganizationID: "org-2", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + _, err = repo.Create(ctx, &types.OrganizationInvitation{ID: inv3ID, Email: "user@example.com", InviterID: user2ID, OrganizationID: org2ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) return repo, ctx @@ -461,14 +506,14 @@ func TestBunOrganizationInvitationRepository_CountByOrganizationIDAndEmail(t *te }, { name: "missing returns zero", - organizationID: "org-1", + organizationID: org1ID, email: "missing@example.com", expectCount: 0, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganization(t, db, "org-2", "user-2", "Beta Inc", "beta-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganization(t, db, org2ID, user2ID, "Beta Inc", "beta-inc") return repositories.NewBunOrganizationInvitationRepository(db), context.Background() }, }, @@ -490,6 +535,11 @@ func TestBunOrganizationInvitationRepository_CountByOrganizationIDAndEmail(t *te func TestBunOrganizationInvitationRepository_WithTx(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + inv1ID := uuid.New().String() + tests := []struct { name string commit bool @@ -500,8 +550,8 @@ func TestBunOrganizationInvitationRepository_WithTx(t *testing.T) { commit: true, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context, repositories.OrganizationInvitationRepository, bun.Tx) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() tx, err := db.BeginTx(ctx, nil) @@ -514,8 +564,8 @@ func TestBunOrganizationInvitationRepository_WithTx(t *testing.T) { commit: false, setup: func(t *testing.T) (repositories.OrganizationInvitationRepository, context.Context, repositories.OrganizationInvitationRepository, bun.Tx) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() tx, err := db.BeginTx(ctx, nil) @@ -533,10 +583,10 @@ func TestBunOrganizationInvitationRepository_WithTx(t *testing.T) { require.NotNil(t, txRepo) require.IsType(t, &repositories.BunOrganizationInvitationRepository{}, txRepo) - created, err := txRepo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + created, err := txRepo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) require.NotNil(t, created) - require.Equal(t, "inv-1", created.ID) + require.Equal(t, inv1ID, created.ID) if tt.commit { require.NoError(t, tx.Commit()) @@ -544,7 +594,7 @@ func TestBunOrganizationInvitationRepository_WithTx(t *testing.T) { require.NoError(t, tx.Rollback()) } - found, err := repo.GetByID(ctx, "inv-1") + found, err := repo.GetByID(ctx, inv1ID) require.NoError(t, err) if tt.commit { require.NotNil(t, found) @@ -558,6 +608,11 @@ func TestBunOrganizationInvitationRepository_WithTx(t *testing.T) { func TestBunOrganizationInvitationRepository_Hooks(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + inv1ID := uuid.New().String() + tests := []struct { name string run func(*testing.T) @@ -566,26 +621,26 @@ func TestBunOrganizationInvitationRepository_Hooks(t *testing.T) { name: "create hooks", run: func(t *testing.T) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") beforeCalled := false afterCalled := false hooks := &plugintests.MockOrganizationInvitationHooks{ Before: func(invitation *types.OrganizationInvitation) error { beforeCalled = true - require.Equal(t, "inv-1", invitation.ID) + require.Equal(t, inv1ID, invitation.ID) return nil }, After: func(invitation types.OrganizationInvitation) error { afterCalled = true - require.Equal(t, "inv-1", invitation.ID) + require.Equal(t, inv1ID, invitation.ID) return nil }, } repo := repositories.NewBunOrganizationInvitationRepository(db, hooks) - created, err := repo.Create(context.Background(), &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + created, err := repo.Create(context.Background(), &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) require.NotNil(t, created) require.True(t, beforeCalled) @@ -596,12 +651,12 @@ func TestBunOrganizationInvitationRepository_Hooks(t *testing.T) { name: "update hooks", run: func(t *testing.T) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") seedRepo := repositories.NewBunOrganizationInvitationRepository(db) ctx := context.Background() - invitation, err := seedRepo.Create(ctx, &types.OrganizationInvitation{ID: "inv-1", Email: "user@example.com", InviterID: "user-1", OrganizationID: "org-1", Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) + invitation, err := seedRepo.Create(ctx, &types.OrganizationInvitation{ID: inv1ID, Email: "user@example.com", InviterID: user1ID, OrganizationID: org1ID, Role: "member", Status: types.OrganizationInvitationStatusPending, ExpiresAt: time.Now().UTC().Add(time.Hour)}) require.NoError(t, err) beforeCalled := false diff --git a/plugins/organizations/repositories/bun_organization_member_repository_test.go b/plugins/organizations/repositories/bun_organization_member_repository_test.go index 0a142c74..d1a78d88 100644 --- a/plugins/organizations/repositories/bun_organization_member_repository_test.go +++ b/plugins/organizations/repositories/bun_organization_member_repository_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/uptrace/bun" @@ -15,6 +16,11 @@ import ( func TestBunOrganizationMemberRepository_CreateGetUpdateDelete(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + mem1ID := uuid.New().String() + tests := []struct { name string run func(*testing.T, repositories.OrganizationMemberRepository, context.Context, *types.OrganizationMember) @@ -23,7 +29,7 @@ func TestBunOrganizationMemberRepository_CreateGetUpdateDelete(t *testing.T) { name: "get by id returns created member", run: func(t *testing.T, orgMemberRepo repositories.OrganizationMemberRepository, ctx context.Context, created *types.OrganizationMember) { t.Helper() - found, err := orgMemberRepo.GetByID(ctx, "mem-1") + found, err := orgMemberRepo.GetByID(ctx, mem1ID) require.NoError(t, err) require.NotNil(t, found) require.Equal(t, created.Role, found.Role) @@ -33,7 +39,7 @@ func TestBunOrganizationMemberRepository_CreateGetUpdateDelete(t *testing.T) { name: "list by organization returns created member", run: func(t *testing.T, orgMemberRepo repositories.OrganizationMemberRepository, ctx context.Context, created *types.OrganizationMember) { t.Helper() - members, err := orgMemberRepo.GetAllByOrganizationID(ctx, "org-1", 1, 10) + members, err := orgMemberRepo.GetAllByOrganizationID(ctx, org1ID, 1, 10) require.NoError(t, err) require.Len(t, members, 1) require.Equal(t, created.ID, members[0].ID) @@ -65,11 +71,11 @@ func TestBunOrganizationMemberRepository_CreateGetUpdateDelete(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") orgMemberRepo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() - created, err := orgMemberRepo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-2", Role: "member"}) + created, err := orgMemberRepo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user2ID, Role: "member"}) require.NoError(t, err) require.NotNil(t, created) @@ -81,6 +87,12 @@ func TestBunOrganizationMemberRepository_CreateGetUpdateDelete(t *testing.T) { func TestBunOrganizationMemberRepository_CountByOrganizationID(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + mem1ID := uuid.New().String() + mem2ID := uuid.New().String() + tests := []struct { name string setup func(*testing.T) (repositories.OrganizationMemberRepository, context.Context) @@ -89,14 +101,14 @@ func TestBunOrganizationMemberRepository_CountByOrganizationID(t *testing.T) { name: "counts members in organization", setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-1", Role: "member"}) + _, err := repo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user1ID, Role: "member"}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationMember{ID: "mem-2", OrganizationID: "org-1", UserID: "user-2", Role: "admin"}) + _, err = repo.Create(ctx, &types.OrganizationMember{ID: mem2ID, OrganizationID: org1ID, UserID: user2ID, Role: "admin"}) require.NoError(t, err) return repo, ctx @@ -109,7 +121,7 @@ func TestBunOrganizationMemberRepository_CountByOrganizationID(t *testing.T) { t.Parallel() repo, ctx := tt.setup(t) - count, err := repo.CountByOrganizationID(ctx, "org-1") + count, err := repo.CountByOrganizationID(ctx, org1ID) require.NoError(t, err) require.Equal(t, 2, count) }) @@ -119,20 +131,26 @@ func TestBunOrganizationMemberRepository_CountByOrganizationID(t *testing.T) { func TestBunOrganizationMemberRepository_Create(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + mem1ID := uuid.New().String() + mem2ID := uuid.New().String() + tests := []struct { name string member *types.OrganizationMember }{ - {name: "member", member: &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-1", Role: "member"}}, - {name: "admin", member: &types.OrganizationMember{ID: "mem-2", OrganizationID: "org-1", UserID: "user-2", Role: "admin"}}, + {name: "member", member: &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user1ID, Role: "member"}}, + {name: "admin", member: &types.OrganizationMember{ID: mem2ID, OrganizationID: org1ID, UserID: user2ID, Role: "admin"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationMemberRepository(db) created, err := repo.Create(context.Background(), tt.member) require.NoError(t, err) @@ -145,6 +163,16 @@ func TestBunOrganizationMemberRepository_Create(t *testing.T) { func TestBunOrganizationMemberRepository_GetAllByOrganizationID(t *testing.T) { t.Parallel() + + user1ID := uuid.New().String() + user2ID := uuid.New().String() + user3ID := uuid.New().String() + org1ID := uuid.New().String() + org2ID := uuid.New().String() + mem1ID := uuid.New().String() + mem2ID := uuid.New().String() + mem3ID := uuid.New().String() + tests := []struct { name string organizationID string @@ -155,22 +183,23 @@ func TestBunOrganizationMemberRepository_GetAllByOrganizationID(t *testing.T) { }{ { name: "first page", - organizationID: "org-1", + organizationID: org1ID, page: 1, limit: 1, expectCount: 1, setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedUser(t, db, user3ID) repo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-1", Role: "member"}) + _, err := repo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user1ID, Role: "member"}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationMember{ID: "mem-2", OrganizationID: "org-1", UserID: "user-2", Role: "admin"}) + _, err = repo.Create(ctx, &types.OrganizationMember{ID: mem2ID, OrganizationID: org1ID, UserID: user2ID, Role: "admin"}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationMember{ID: "mem-3", OrganizationID: "org-1", UserID: "user-3", Role: "member"}) + _, err = repo.Create(ctx, &types.OrganizationMember{ID: mem3ID, OrganizationID: org1ID, UserID: user3ID, Role: "member"}) require.NoError(t, err) return repo, ctx @@ -178,22 +207,23 @@ func TestBunOrganizationMemberRepository_GetAllByOrganizationID(t *testing.T) { }, { name: "second page", - organizationID: "org-1", + organizationID: org1ID, page: 2, limit: 1, expectCount: 1, setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedUser(t, db, user3ID) repo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-1", Role: "member"}) + _, err := repo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user1ID, Role: "member"}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationMember{ID: "mem-2", OrganizationID: "org-1", UserID: "user-2", Role: "admin"}) + _, err = repo.Create(ctx, &types.OrganizationMember{ID: mem2ID, OrganizationID: org1ID, UserID: user2ID, Role: "admin"}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationMember{ID: "mem-3", OrganizationID: "org-1", UserID: "user-3", Role: "member"}) + _, err = repo.Create(ctx, &types.OrganizationMember{ID: mem3ID, OrganizationID: org1ID, UserID: user3ID, Role: "member"}) require.NoError(t, err) return repo, ctx @@ -201,14 +231,14 @@ func TestBunOrganizationMemberRepository_GetAllByOrganizationID(t *testing.T) { }, { name: "empty result", - organizationID: "org-2", + organizationID: org2ID, page: 1, limit: 10, expectCount: 0, setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationMemberRepository(db) return repo, context.Background() }, @@ -225,10 +255,10 @@ func TestBunOrganizationMemberRepository_GetAllByOrganizationID(t *testing.T) { require.NoError(t, err) require.Len(t, members, tt.expectCount) if tt.page == 1 && len(members) > 0 { - require.Equal(t, "mem-3", members[0].ID) + require.Equal(t, mem3ID, members[0].ID) } if tt.page == 2 && len(members) > 0 { - require.Equal(t, "mem-2", members[0].ID) + require.Equal(t, mem2ID, members[0].ID) } }) } @@ -236,6 +266,15 @@ func TestBunOrganizationMemberRepository_GetAllByOrganizationID(t *testing.T) { func TestBunOrganizationMemberRepository_GetAllByUserID(t *testing.T) { t.Parallel() + + user1ID := uuid.New().String() + user2ID := uuid.New().String() + user3ID := uuid.New().String() + org1ID := uuid.New().String() + org2ID := uuid.New().String() + mem1ID := uuid.New().String() + mem2ID := uuid.New().String() + tests := []struct { name string userID string @@ -244,19 +283,19 @@ func TestBunOrganizationMemberRepository_GetAllByUserID(t *testing.T) { }{ { name: "found", - userID: "user-1", + userID: user1ID, expectCount: 2, setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganization(t, db, "org-2", "user-2", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganization(t, db, org2ID, user2ID, "Platform", "platform") repo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-1", Role: "member"}) + _, err := repo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user1ID, Role: "member"}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationMember{ID: "mem-2", OrganizationID: "org-2", UserID: "user-1", Role: "admin"}) + _, err = repo.Create(ctx, &types.OrganizationMember{ID: mem2ID, OrganizationID: org2ID, UserID: user1ID, Role: "admin"}) require.NoError(t, err) return repo, ctx @@ -264,13 +303,13 @@ func TestBunOrganizationMemberRepository_GetAllByUserID(t *testing.T) { }, { name: "empty", - userID: "user-3", + userID: user3ID, expectCount: 0, setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganization(t, db, "org-2", "user-2", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganization(t, db, org2ID, user2ID, "Platform", "platform") return repositories.NewBunOrganizationMemberRepository(db), context.Background() }, }, @@ -291,6 +330,12 @@ func TestBunOrganizationMemberRepository_GetAllByUserID(t *testing.T) { func TestBunOrganizationMemberRepository_GetByOrganizationIDAndUserID(t *testing.T) { t.Parallel() + + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + mem1ID := uuid.New().String() + tests := []struct { name string organizationID string @@ -300,17 +345,17 @@ func TestBunOrganizationMemberRepository_GetByOrganizationIDAndUserID(t *testing }{ { name: "found", - organizationID: "org-1", - userID: "user-1", + organizationID: org1ID, + userID: user1ID, expectFound: true, setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-1", Role: "member"}) + _, err := repo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user1ID, Role: "member"}) require.NoError(t, err) return repo, ctx @@ -318,12 +363,12 @@ func TestBunOrganizationMemberRepository_GetByOrganizationIDAndUserID(t *testing }, { name: "not found", - organizationID: "org-1", - userID: "user-2", + organizationID: org1ID, + userID: user2ID, setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") return repositories.NewBunOrganizationMemberRepository(db), context.Background() }, }, @@ -339,7 +384,7 @@ func TestBunOrganizationMemberRepository_GetByOrganizationIDAndUserID(t *testing require.NoError(t, err) if tt.expectFound { require.NotNil(t, found) - require.Equal(t, "mem-1", found.ID) + require.Equal(t, mem1ID, found.ID) return } require.Nil(t, found) @@ -349,6 +394,12 @@ func TestBunOrganizationMemberRepository_GetByOrganizationIDAndUserID(t *testing func TestBunOrganizationMemberRepository_GetByID(t *testing.T) { t.Parallel() + + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + mem1ID := uuid.New().String() + tests := []struct { name string memberID string @@ -357,16 +408,16 @@ func TestBunOrganizationMemberRepository_GetByID(t *testing.T) { }{ { name: "found", - memberID: "mem-1", + memberID: mem1ID, expectFound: true, setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-1", Role: "member"}) + _, err := repo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user1ID, Role: "member"}) require.NoError(t, err) return repo, ctx @@ -374,10 +425,10 @@ func TestBunOrganizationMemberRepository_GetByID(t *testing.T) { }, { name: "not found", - memberID: "missing", + memberID: "00000000-0000-0000-0000-000000000000", setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context) { t.Helper() - return repositories.NewBunOrganizationMemberRepository(plugintests.SetupRepoDB(t)), context.Background() + return repositories.NewBunOrganizationMemberRepository(plugintests.SetupRepoDB(t, user1ID, user2ID)), context.Background() }, }, } @@ -392,7 +443,7 @@ func TestBunOrganizationMemberRepository_GetByID(t *testing.T) { require.NoError(t, err) if tt.expectFound { require.NotNil(t, found) - require.Equal(t, "mem-1", found.ID) + require.Equal(t, mem1ID, found.ID) return } require.Nil(t, found) @@ -402,6 +453,12 @@ func TestBunOrganizationMemberRepository_GetByID(t *testing.T) { func TestBunOrganizationMemberRepository_Update(t *testing.T) { t.Parallel() + + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + mem1ID := uuid.New().String() + tests := []struct { name string setup func(*testing.T) (repositories.OrganizationMemberRepository, context.Context, *types.OrganizationMember) @@ -410,12 +467,12 @@ func TestBunOrganizationMemberRepository_Update(t *testing.T) { name: "change role", setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context, *types.OrganizationMember) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() - created, err := repo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-1", Role: "member"}) + created, err := repo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user1ID, Role: "member"}) require.NoError(t, err) return repo, ctx, created }, @@ -437,6 +494,12 @@ func TestBunOrganizationMemberRepository_Update(t *testing.T) { func TestBunOrganizationMemberRepository_Delete(t *testing.T) { t.Parallel() + + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + mem1ID := uuid.New().String() + tests := []struct { name string memberID string @@ -444,15 +507,15 @@ func TestBunOrganizationMemberRepository_Delete(t *testing.T) { }{ { name: "delete existing", - memberID: "mem-1", + memberID: mem1ID, setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-1", Role: "member"}) + _, err := repo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user1ID, Role: "member"}) require.NoError(t, err) return repo, ctx }, @@ -476,6 +539,11 @@ func TestBunOrganizationMemberRepository_Delete(t *testing.T) { func TestBunOrganizationMemberRepository_WithTx(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + mem1ID := uuid.New().String() + tests := []struct { name string commit bool @@ -486,8 +554,8 @@ func TestBunOrganizationMemberRepository_WithTx(t *testing.T) { commit: true, setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context, repositories.OrganizationMemberRepository, bun.Tx) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() tx, err := db.BeginTx(ctx, nil) @@ -500,8 +568,8 @@ func TestBunOrganizationMemberRepository_WithTx(t *testing.T) { commit: false, setup: func(t *testing.T) (repositories.OrganizationMemberRepository, context.Context, repositories.OrganizationMemberRepository, bun.Tx) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() tx, err := db.BeginTx(ctx, nil) @@ -519,10 +587,10 @@ func TestBunOrganizationMemberRepository_WithTx(t *testing.T) { require.NotNil(t, txRepo) require.IsType(t, &repositories.BunOrganizationMemberRepository{}, txRepo) - created, err := txRepo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-1", Role: "member"}) + created, err := txRepo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user1ID, Role: "member"}) require.NoError(t, err) require.NotNil(t, created) - require.Equal(t, "mem-1", created.ID) + require.Equal(t, mem1ID, created.ID) if tt.commit { require.NoError(t, tx.Commit()) @@ -530,7 +598,7 @@ func TestBunOrganizationMemberRepository_WithTx(t *testing.T) { require.NoError(t, tx.Rollback()) } - found, err := repo.GetByID(ctx, "mem-1") + found, err := repo.GetByID(ctx, mem1ID) require.NoError(t, err) if tt.commit { require.NotNil(t, found) @@ -544,6 +612,11 @@ func TestBunOrganizationMemberRepository_WithTx(t *testing.T) { func TestBunOrganizationMemberRepository_Hooks(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + mem1ID := uuid.New().String() + tests := []struct { name string run func(*testing.T) @@ -552,26 +625,26 @@ func TestBunOrganizationMemberRepository_Hooks(t *testing.T) { name: "create hooks", run: func(t *testing.T) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") beforeCalled := false afterCalled := false hooks := &plugintests.MockOrganizationMemberHooks{ Before: func(member *types.OrganizationMember) error { beforeCalled = true - require.Equal(t, "mem-1", member.ID) + require.Equal(t, mem1ID, member.ID) return nil }, After: func(member types.OrganizationMember) error { afterCalled = true - require.Equal(t, "mem-1", member.ID) + require.Equal(t, mem1ID, member.ID) return nil }, } repo := repositories.NewBunOrganizationMemberRepository(db, hooks) - created, err := repo.Create(context.Background(), &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-2", Role: "member"}) + created, err := repo.Create(context.Background(), &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user2ID, Role: "member"}) require.NoError(t, err) require.NotNil(t, created) require.True(t, beforeCalled) @@ -582,12 +655,12 @@ func TestBunOrganizationMemberRepository_Hooks(t *testing.T) { name: "update hooks", run: func(t *testing.T) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") seedRepo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() - member, err := seedRepo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-2", Role: "member"}) + member, err := seedRepo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user2ID, Role: "member"}) require.NoError(t, err) beforeCalled := false @@ -618,12 +691,12 @@ func TestBunOrganizationMemberRepository_Hooks(t *testing.T) { name: "delete hooks", run: func(t *testing.T) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") seedRepo := repositories.NewBunOrganizationMemberRepository(db) ctx := context.Background() - _, err := seedRepo.Create(ctx, &types.OrganizationMember{ID: "mem-1", OrganizationID: "org-1", UserID: "user-2", Role: "member"}) + _, err := seedRepo.Create(ctx, &types.OrganizationMember{ID: mem1ID, OrganizationID: org1ID, UserID: user2ID, Role: "member"}) require.NoError(t, err) beforeCalled := false @@ -631,19 +704,19 @@ func TestBunOrganizationMemberRepository_Hooks(t *testing.T) { hooks := &plugintests.MockOrganizationMemberHooks{ BeforeDelete: func(member *types.OrganizationMember) error { beforeCalled = true - require.Equal(t, "mem-1", member.ID) + require.Equal(t, mem1ID, member.ID) return nil }, AfterDelete: func(member types.OrganizationMember) error { afterCalled = true - require.Equal(t, "mem-1", member.ID) + require.Equal(t, mem1ID, member.ID) return nil }, } repo := repositories.NewBunOrganizationMemberRepository(db, hooks) - require.NoError(t, repo.Delete(ctx, "mem-1")) - found, err := repo.GetByID(ctx, "mem-1") + require.NoError(t, repo.Delete(ctx, mem1ID)) + found, err := repo.GetByID(ctx, mem1ID) require.NoError(t, err) require.Nil(t, found) require.True(t, beforeCalled) diff --git a/plugins/organizations/repositories/bun_organization_repository_test.go b/plugins/organizations/repositories/bun_organization_repository_test.go index 44c7c1c9..4ae1d1d4 100644 --- a/plugins/organizations/repositories/bun_organization_repository_test.go +++ b/plugins/organizations/repositories/bun_organization_repository_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/uptrace/bun" @@ -15,17 +16,22 @@ import ( func TestBunOrganizationRepository_Create(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + org2ID := uuid.New().String() + tests := []struct { name string organization *types.Organization }{ { name: "keeps provided metadata", - organization: &types.Organization{ID: "org-1", OwnerID: "user-1", Name: "Acme Inc", Slug: "acme-inc", Metadata: map[string]any{"tier": "core"}}, + organization: &types.Organization{ID: org1ID, OwnerID: user1ID, Name: "Acme Inc", Slug: "acme-inc", Metadata: map[string]any{"tier": "core"}}, }, { name: "keeps alternate metadata", - organization: &types.Organization{ID: "org-2", OwnerID: "user-1", Name: "Platform", Slug: "platform", Metadata: map[string]any{"tier": "platform"}}, + organization: &types.Organization{ID: org2ID, OwnerID: user1ID, Name: "Platform", Slug: "platform", Metadata: map[string]any{"tier": "platform"}}, }, } @@ -33,7 +39,7 @@ func TestBunOrganizationRepository_Create(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := repositories.NewBunOrganizationRepository(db) created, err := repo.Create(context.Background(), tt.organization) @@ -48,6 +54,10 @@ func TestBunOrganizationRepository_Create(t *testing.T) { func TestBunOrganizationRepository_GetByID(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + tests := []struct { name string organizationID string @@ -56,15 +66,15 @@ func TestBunOrganizationRepository_GetByID(t *testing.T) { }{ { name: "found", - organizationID: "org-1", + organizationID: org1ID, expectFound: true, setup: func(t *testing.T) (repositories.OrganizationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := repositories.NewBunOrganizationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.Organization{ID: "org-1", OwnerID: "user-1", Name: "Acme Inc", Slug: "acme-inc", Metadata: map[string]any{"tier": "core"}}) + _, err := repo.Create(ctx, &types.Organization{ID: org1ID, OwnerID: user1ID, Name: "Acme Inc", Slug: "acme-inc", Metadata: map[string]any{"tier": "core"}}) require.NoError(t, err) return repo, ctx @@ -72,10 +82,10 @@ func TestBunOrganizationRepository_GetByID(t *testing.T) { }, { name: "not found", - organizationID: "missing", + organizationID: "00000000-0000-0000-0000-000000000000", setup: func(t *testing.T) (repositories.OrganizationRepository, context.Context) { t.Helper() - return repositories.NewBunOrganizationRepository(plugintests.SetupRepoDB(t)), context.Background() + return repositories.NewBunOrganizationRepository(plugintests.SetupRepoDB(t, user1ID, user2ID)), context.Background() }, }, } @@ -90,8 +100,8 @@ func TestBunOrganizationRepository_GetByID(t *testing.T) { require.NoError(t, err) if tt.expectFound { require.NotNil(t, found) - require.Equal(t, "org-1", found.ID) - require.Equal(t, "user-1", found.OwnerID) + require.Equal(t, org1ID, found.ID) + require.Equal(t, user1ID, found.OwnerID) return } require.Nil(t, found) @@ -102,6 +112,10 @@ func TestBunOrganizationRepository_GetByID(t *testing.T) { func TestBunOrganizationRepository_GetBySlug(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + tests := []struct { name string slug string @@ -114,11 +128,11 @@ func TestBunOrganizationRepository_GetBySlug(t *testing.T) { expectFound: true, setup: func(t *testing.T) (repositories.OrganizationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := repositories.NewBunOrganizationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.Organization{ID: "org-1", OwnerID: "user-1", Name: "Acme Inc", Slug: "acme-inc", Metadata: map[string]any{"tier": "core"}}) + _, err := repo.Create(ctx, &types.Organization{ID: org1ID, OwnerID: user1ID, Name: "Acme Inc", Slug: "acme-inc", Metadata: map[string]any{"tier": "core"}}) require.NoError(t, err) return repo, ctx @@ -129,7 +143,7 @@ func TestBunOrganizationRepository_GetBySlug(t *testing.T) { slug: "missing", setup: func(t *testing.T) (repositories.OrganizationRepository, context.Context) { t.Helper() - return repositories.NewBunOrganizationRepository(plugintests.SetupRepoDB(t)), context.Background() + return repositories.NewBunOrganizationRepository(plugintests.SetupRepoDB(t, user1ID, user2ID)), context.Background() }, }, } @@ -144,7 +158,7 @@ func TestBunOrganizationRepository_GetBySlug(t *testing.T) { require.NoError(t, err) if tt.expectFound { require.NotNil(t, found) - require.Equal(t, "org-1", found.ID) + require.Equal(t, org1ID, found.ID) return } require.Nil(t, found) @@ -155,6 +169,11 @@ func TestBunOrganizationRepository_GetBySlug(t *testing.T) { func TestBunOrganizationRepository_GetAllByOwnerID(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + org2ID := uuid.New().String() + tests := []struct { name string ownerID string @@ -163,17 +182,17 @@ func TestBunOrganizationRepository_GetAllByOwnerID(t *testing.T) { }{ { name: "found", - ownerID: "user-1", + ownerID: user1ID, expectCount: 2, setup: func(t *testing.T) (repositories.OrganizationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := repositories.NewBunOrganizationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.Organization{ID: "org-1", OwnerID: "user-1", Name: "Acme Inc", Slug: "acme-inc"}) + _, err := repo.Create(ctx, &types.Organization{ID: org1ID, OwnerID: user1ID, Name: "Acme Inc", Slug: "acme-inc"}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.Organization{ID: "org-2", OwnerID: "user-1", Name: "Platform", Slug: "platform"}) + _, err = repo.Create(ctx, &types.Organization{ID: org2ID, OwnerID: user1ID, Name: "Platform", Slug: "platform"}) require.NoError(t, err) return repo, ctx @@ -181,11 +200,11 @@ func TestBunOrganizationRepository_GetAllByOwnerID(t *testing.T) { }, { name: "empty", - ownerID: "user-2", + ownerID: user2ID, expectCount: 0, setup: func(t *testing.T) (repositories.OrganizationRepository, context.Context) { t.Helper() - return repositories.NewBunOrganizationRepository(plugintests.SetupRepoDB(t)), context.Background() + return repositories.NewBunOrganizationRepository(plugintests.SetupRepoDB(t, user1ID, user2ID)), context.Background() }, }, } @@ -206,6 +225,10 @@ func TestBunOrganizationRepository_GetAllByOwnerID(t *testing.T) { func TestBunOrganizationRepository_Update(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + tests := []struct { name string setup func(*testing.T) (repositories.OrganizationRepository, context.Context, *types.Organization) @@ -214,11 +237,11 @@ func TestBunOrganizationRepository_Update(t *testing.T) { name: "update name and logo", setup: func(t *testing.T) (repositories.OrganizationRepository, context.Context, *types.Organization) { t.Helper() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := repositories.NewBunOrganizationRepository(db) ctx := context.Background() - created, err := repo.Create(ctx, &types.Organization{ID: "org-1", OwnerID: "user-1", Name: "Acme Inc", Slug: "acme-inc", Metadata: map[string]any{"tier": "core"}}) + created, err := repo.Create(ctx, &types.Organization{ID: org1ID, OwnerID: user1ID, Name: "Acme Inc", Slug: "acme-inc", Metadata: map[string]any{"tier": "core"}}) require.NoError(t, err) return repo, ctx, created }, @@ -246,6 +269,10 @@ func TestBunOrganizationRepository_Update(t *testing.T) { func TestBunOrganizationRepository_Delete(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + tests := []struct { name string organizationID string @@ -253,14 +280,14 @@ func TestBunOrganizationRepository_Delete(t *testing.T) { }{ { name: "delete existing", - organizationID: "org-1", + organizationID: org1ID, setup: func(t *testing.T) (repositories.OrganizationRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := repositories.NewBunOrganizationRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.Organization{ID: "org-1", OwnerID: "user-1", Name: "Acme Inc", Slug: "acme-inc"}) + _, err := repo.Create(ctx, &types.Organization{ID: org1ID, OwnerID: user1ID, Name: "Acme Inc", Slug: "acme-inc"}) require.NoError(t, err) return repo, ctx @@ -285,6 +312,10 @@ func TestBunOrganizationRepository_Delete(t *testing.T) { func TestBunOrganizationRepository_WithTx(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + tests := []struct { name string commit bool @@ -295,7 +326,7 @@ func TestBunOrganizationRepository_WithTx(t *testing.T) { commit: true, setup: func(t *testing.T) (repositories.OrganizationRepository, context.Context, repositories.OrganizationRepository, bun.Tx) { t.Helper() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := repositories.NewBunOrganizationRepository(db) ctx := context.Background() tx, err := db.BeginTx(ctx, nil) @@ -308,7 +339,7 @@ func TestBunOrganizationRepository_WithTx(t *testing.T) { commit: false, setup: func(t *testing.T) (repositories.OrganizationRepository, context.Context, repositories.OrganizationRepository, bun.Tx) { t.Helper() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) repo := repositories.NewBunOrganizationRepository(db) ctx := context.Background() tx, err := db.BeginTx(ctx, nil) @@ -326,10 +357,10 @@ func TestBunOrganizationRepository_WithTx(t *testing.T) { require.NotNil(t, txRepo) require.IsType(t, &repositories.BunOrganizationRepository{}, txRepo) - created, err := txRepo.Create(ctx, &types.Organization{ID: "org-1", OwnerID: "user-1", Name: "Acme Inc", Slug: "acme-inc", Metadata: map[string]any{"tier": "core"}}) + created, err := txRepo.Create(ctx, &types.Organization{ID: org1ID, OwnerID: user1ID, Name: "Acme Inc", Slug: "acme-inc", Metadata: map[string]any{"tier": "core"}}) require.NoError(t, err) require.NotNil(t, created) - require.Equal(t, "org-1", created.ID) + require.Equal(t, org1ID, created.ID) if tt.commit { require.NoError(t, tx.Commit()) @@ -337,7 +368,7 @@ func TestBunOrganizationRepository_WithTx(t *testing.T) { require.NoError(t, tx.Rollback()) } - found, err := repo.GetByID(ctx, "org-1") + found, err := repo.GetByID(ctx, org1ID) require.NoError(t, err) if tt.commit { require.NotNil(t, found) @@ -351,6 +382,10 @@ func TestBunOrganizationRepository_WithTx(t *testing.T) { func TestBunOrganizationRepository_CreateHooks(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + tests := []struct { name string setup func(*testing.T) (*repositories.BunOrganizationRepository, context.Context, *types.Organization) @@ -361,23 +396,23 @@ func TestBunOrganizationRepository_CreateHooks(t *testing.T) { name: "hooks are called on create", setup: func(t *testing.T) (*repositories.BunOrganizationRepository, context.Context, *types.Organization) { t.Helper() - db := plugintests.SetupRepoDB(t) + db := plugintests.SetupRepoDB(t, user1ID, user2ID) var beforeCalled bool var afterCalled bool hooks := &plugintests.MockOrganizationHooks{ BeforeCreate: func(organization *types.Organization) error { beforeCalled = true - require.Equal(t, "org-1", organization.ID) + require.Equal(t, org1ID, organization.ID) return nil }, AfterCreate: func(organization types.Organization) error { afterCalled = true - require.Equal(t, "org-1", organization.ID) + require.Equal(t, org1ID, organization.ID) return nil }, } repo := repositories.NewBunOrganizationRepository(db, hooks).(*repositories.BunOrganizationRepository) - org := &types.Organization{ID: "org-1", OwnerID: "user-1", Name: "Acme Inc", Slug: "acme-inc"} + org := &types.Organization{ID: org1ID, OwnerID: user1ID, Name: "Acme Inc", Slug: "acme-inc"} t.Cleanup(func() { require.True(t, beforeCalled) require.True(t, afterCalled) diff --git a/plugins/organizations/repositories/bun_organization_team_member_repository_test.go b/plugins/organizations/repositories/bun_organization_team_member_repository_test.go index 4f99734d..bb2d19f5 100644 --- a/plugins/organizations/repositories/bun_organization_team_member_repository_test.go +++ b/plugins/organizations/repositories/bun_organization_team_member_repository_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/uptrace/bun" @@ -15,6 +16,15 @@ import ( func TestBunOrganizationTeamMemberRepository_Create(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + member1ID := uuid.New().String() + member2ID := uuid.New().String() + team1ID := uuid.New().String() + teamMember1ID := uuid.New().String() + teamMember2ID := uuid.New().String() + tests := []struct { name string teamMember *types.OrganizationTeamMember @@ -23,41 +33,41 @@ func TestBunOrganizationTeamMemberRepository_Create(t *testing.T) { }{ { name: "team member", - teamMember: &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}, + teamMember: &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") return repositories.NewBunOrganizationTeamMemberRepository(db), context.Background() }, }, { name: "another team member", - teamMember: &types.OrganizationTeamMember{ID: "team-member-2", TeamID: "team-1", MemberID: "member-2"}, + teamMember: &types.OrganizationTeamMember{ID: teamMember2ID, TeamID: team1ID, MemberID: member2ID}, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") return repositories.NewBunOrganizationTeamMemberRepository(db), context.Background() }, }, { name: "duplicate id returns error", - teamMember: &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}, + teamMember: &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}, expectErr: true, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") return repositories.NewBunOrganizationTeamMemberRepository(db), context.Background() }, }, @@ -94,6 +104,14 @@ func TestBunOrganizationTeamMemberRepository_Create(t *testing.T) { func TestBunOrganizationTeamMemberRepository_GetByID(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + member1ID := uuid.New().String() + member2ID := uuid.New().String() + team1ID := uuid.New().String() + teamMember1ID := uuid.New().String() + tests := []struct { name string teamMemberID string @@ -102,28 +120,28 @@ func TestBunOrganizationTeamMemberRepository_GetByID(t *testing.T) { }{ { name: "found", - teamMemberID: "team-member-1", + teamMemberID: teamMember1ID, expectFound: true, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") repo := repositories.NewBunOrganizationTeamMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}) + _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}) require.NoError(t, err) return repo, ctx }, }, { name: "not found", - teamMemberID: "missing", + teamMemberID: "00000000-0000-0000-0000-000000000000", setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - return repositories.NewBunOrganizationTeamMemberRepository(plugintests.SetupRepoDB(t)), context.Background() + return repositories.NewBunOrganizationTeamMemberRepository(plugintests.SetupRepoDB(t, user1ID, user2ID)), context.Background() }, }, } @@ -138,7 +156,7 @@ func TestBunOrganizationTeamMemberRepository_GetByID(t *testing.T) { require.NoError(t, err) if tt.expectFound { require.NotNil(t, found) - require.Equal(t, "team-member-1", found.ID) + require.Equal(t, teamMember1ID, found.ID) return } require.Nil(t, found) @@ -149,6 +167,15 @@ func TestBunOrganizationTeamMemberRepository_GetByID(t *testing.T) { func TestBunOrganizationTeamMemberRepository_GetByTeamIDAndMemberID(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + member1ID := uuid.New().String() + member2ID := uuid.New().String() + team1ID := uuid.New().String() + team2ID := uuid.New().String() + teamMember1ID := uuid.New().String() + tests := []struct { name string teamID string @@ -158,58 +185,58 @@ func TestBunOrganizationTeamMemberRepository_GetByTeamIDAndMemberID(t *testing.T }{ { name: "found", - teamID: "team-1", - memberID: "member-1", + teamID: team1ID, + memberID: member1ID, expectFound: true, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") - plugintests.SeedOrganizationTeam(t, db, "team-2", "org-1", "Core", "core") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") + plugintests.SeedOrganizationTeam(t, db, team2ID, org1ID, "Core", "core") repo := repositories.NewBunOrganizationTeamMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}) + _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}) require.NoError(t, err) return repo, ctx }, }, { name: "wrong member", - teamID: "team-1", - memberID: "member-2", + teamID: team1ID, + memberID: member2ID, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") - plugintests.SeedOrganizationTeam(t, db, "team-2", "org-1", "Core", "core") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") + plugintests.SeedOrganizationTeam(t, db, team2ID, org1ID, "Core", "core") repo := repositories.NewBunOrganizationTeamMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}) + _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}) require.NoError(t, err) return repo, ctx }, }, { name: "wrong team", - teamID: "team-2", - memberID: "member-1", + teamID: team2ID, + memberID: member1ID, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") - plugintests.SeedOrganizationTeam(t, db, "team-2", "org-1", "Core", "core") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") + plugintests.SeedOrganizationTeam(t, db, team2ID, org1ID, "Core", "core") repo := repositories.NewBunOrganizationTeamMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}) + _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}) require.NoError(t, err) return repo, ctx }, @@ -226,7 +253,7 @@ func TestBunOrganizationTeamMemberRepository_GetByTeamIDAndMemberID(t *testing.T require.NoError(t, err) if tt.expectFound { require.NotNil(t, found) - require.Equal(t, "team-member-1", found.ID) + require.Equal(t, teamMember1ID, found.ID) return } require.Nil(t, found) @@ -237,6 +264,19 @@ func TestBunOrganizationTeamMemberRepository_GetByTeamIDAndMemberID(t *testing.T func TestBunOrganizationTeamMemberRepository_GetAllByTeamID(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + user3ID := uuid.New().String() + org1ID := uuid.New().String() + member1ID := uuid.New().String() + member2ID := uuid.New().String() + member3ID := uuid.New().String() + team1ID := uuid.New().String() + team2ID := uuid.New().String() + teamMember1ID := uuid.New().String() + teamMember2ID := uuid.New().String() + teamMember3ID := uuid.New().String() + tests := []struct { name string teamID string @@ -247,71 +287,71 @@ func TestBunOrganizationTeamMemberRepository_GetAllByTeamID(t *testing.T) { }{ { name: "first page", - teamID: "team-1", + teamID: team1ID, page: 1, limit: 2, expectCount: 2, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedUser(t, db, "user-3") - plugintests.SeedOrganizationMember(t, db, "member-3", "org-1", "user-3", "member") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedUser(t, db, user3ID) + plugintests.SeedOrganizationMember(t, db, member3ID, org1ID, user3ID, "member") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") repo := repositories.NewBunOrganizationTeamMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}) + _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-2", TeamID: "team-1", MemberID: "member-2"}) + _, err = repo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember2ID, TeamID: team1ID, MemberID: member2ID}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-3", TeamID: "team-1", MemberID: "member-3"}) + _, err = repo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember3ID, TeamID: team1ID, MemberID: member3ID}) require.NoError(t, err) return repo, ctx }, }, { name: "second page", - teamID: "team-1", + teamID: team1ID, page: 2, limit: 2, expectCount: 1, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedUser(t, db, "user-3") - plugintests.SeedOrganizationMember(t, db, "member-3", "org-1", "user-3", "member") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedUser(t, db, user3ID) + plugintests.SeedOrganizationMember(t, db, member3ID, org1ID, user3ID, "member") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") repo := repositories.NewBunOrganizationTeamMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}) + _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-2", TeamID: "team-1", MemberID: "member-2"}) + _, err = repo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember2ID, TeamID: team1ID, MemberID: member2ID}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-3", TeamID: "team-1", MemberID: "member-3"}) + _, err = repo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember3ID, TeamID: team1ID, MemberID: member3ID}) require.NoError(t, err) return repo, ctx }, }, { name: "empty", - teamID: "team-2", + teamID: team2ID, page: 1, limit: 10, expectCount: 0, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedUser(t, db, "user-3") - plugintests.SeedOrganizationMember(t, db, "member-3", "org-1", "user-3", "member") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedUser(t, db, user3ID) + plugintests.SeedOrganizationMember(t, db, member3ID, org1ID, user3ID, "member") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") return repositories.NewBunOrganizationTeamMemberRepository(db), context.Background() }, }, @@ -327,14 +367,14 @@ func TestBunOrganizationTeamMemberRepository_GetAllByTeamID(t *testing.T) { require.NoError(t, err) require.Len(t, found, tt.expectCount) for _, teamMember := range found { - require.Equal(t, "team-1", teamMember.TeamID) + require.Equal(t, team1ID, teamMember.TeamID) } if tt.page == 1 && len(found) > 1 { - require.Equal(t, "team-member-3", found[0].ID) - require.Equal(t, "team-member-2", found[1].ID) + require.Equal(t, teamMember3ID, found[0].ID) + require.Equal(t, teamMember2ID, found[1].ID) } if tt.page == 2 && len(found) > 0 { - require.Equal(t, "team-member-1", found[0].ID) + require.Equal(t, teamMember1ID, found[0].ID) } }) } @@ -343,6 +383,14 @@ func TestBunOrganizationTeamMemberRepository_GetAllByTeamID(t *testing.T) { func TestBunOrganizationTeamMemberRepository_DeleteByTeamIDAndMemberID(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + member1ID := uuid.New().String() + member2ID := uuid.New().String() + team1ID := uuid.New().String() + teamMember1ID := uuid.New().String() + tests := []struct { name string teamID string @@ -351,33 +399,33 @@ func TestBunOrganizationTeamMemberRepository_DeleteByTeamIDAndMemberID(t *testin }{ { name: "delete existing", - teamID: "team-1", - memberID: "member-1", + teamID: team1ID, + memberID: member1ID, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") repo := repositories.NewBunOrganizationTeamMemberRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}) + _, err := repo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}) require.NoError(t, err) return repo, ctx }, }, { name: "delete missing", - teamID: "team-1", - memberID: "missing", + teamID: team1ID, + memberID: "00000000-0000-0000-0000-000000000000", setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationMember(t, db, "member-2", "org-1", "user-2", "admin") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationMember(t, db, member2ID, org1ID, user2ID, "admin") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") return repositories.NewBunOrganizationTeamMemberRepository(db), context.Background() }, }, @@ -400,6 +448,13 @@ func TestBunOrganizationTeamMemberRepository_DeleteByTeamIDAndMemberID(t *testin func TestBunOrganizationTeamMemberRepository_WithTx(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + member1ID := uuid.New().String() + team1ID := uuid.New().String() + teamMember1ID := uuid.New().String() + tests := []struct { name string commit bool @@ -410,10 +465,10 @@ func TestBunOrganizationTeamMemberRepository_WithTx(t *testing.T) { commit: true, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context, repositories.OrganizationTeamMemberRepository, bun.Tx) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") repo := repositories.NewBunOrganizationTeamMemberRepository(db) ctx := context.Background() @@ -430,10 +485,10 @@ func TestBunOrganizationTeamMemberRepository_WithTx(t *testing.T) { commit: false, setup: func(t *testing.T) (repositories.OrganizationTeamMemberRepository, context.Context, repositories.OrganizationTeamMemberRepository, bun.Tx) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") repo := repositories.NewBunOrganizationTeamMemberRepository(db) ctx := context.Background() @@ -452,10 +507,10 @@ func TestBunOrganizationTeamMemberRepository_WithTx(t *testing.T) { t.Parallel() repo, ctx, txRepo, tx := tt.setup(t) - created, err := txRepo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}) + created, err := txRepo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}) require.NoError(t, err) require.NotNil(t, created) - require.Equal(t, "team-member-1", created.ID) + require.Equal(t, teamMember1ID, created.ID) if tt.commit { require.NoError(t, tx.Commit()) @@ -463,11 +518,11 @@ func TestBunOrganizationTeamMemberRepository_WithTx(t *testing.T) { require.NoError(t, tx.Rollback()) } - found, err := repo.GetByID(ctx, "team-member-1") + found, err := repo.GetByID(ctx, teamMember1ID) require.NoError(t, err) if tt.commit { require.NotNil(t, found) - require.Equal(t, "team-member-1", found.ID) + require.Equal(t, teamMember1ID, found.ID) } else { require.Nil(t, found) } @@ -478,6 +533,13 @@ func TestBunOrganizationTeamMemberRepository_WithTx(t *testing.T) { func TestBunOrganizationTeamMemberRepository_Hooks(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + member1ID := uuid.New().String() + team1ID := uuid.New().String() + teamMember1ID := uuid.New().String() + tests := []struct { name string run func(*testing.T) @@ -486,28 +548,28 @@ func TestBunOrganizationTeamMemberRepository_Hooks(t *testing.T) { name: "create hooks", run: func(t *testing.T) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") beforeCalled := false afterCalled := false hooks := &plugintests.MockOrganizationTeamMemberHooks{ BeforeCreate: func(teamMember *types.OrganizationTeamMember) error { beforeCalled = true - require.Equal(t, "team-member-1", teamMember.ID) + require.Equal(t, teamMember1ID, teamMember.ID) return nil }, AfterCreate: func(teamMember types.OrganizationTeamMember) error { afterCalled = true - require.Equal(t, "team-member-1", teamMember.ID) + require.Equal(t, teamMember1ID, teamMember.ID) return nil }, } repo := repositories.NewBunOrganizationTeamMemberRepository(db, hooks) - created, err := repo.Create(context.Background(), &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}) + created, err := repo.Create(context.Background(), &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}) require.NoError(t, err) require.NotNil(t, created) require.True(t, beforeCalled) @@ -518,14 +580,14 @@ func TestBunOrganizationTeamMemberRepository_Hooks(t *testing.T) { name: "delete hooks", run: func(t *testing.T) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") - plugintests.SeedOrganizationMember(t, db, "member-1", "org-1", "user-1", "member") - plugintests.SeedOrganizationTeam(t, db, "team-1", "org-1", "Platform", "platform") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") + plugintests.SeedOrganizationMember(t, db, member1ID, org1ID, user1ID, "member") + plugintests.SeedOrganizationTeam(t, db, team1ID, org1ID, "Platform", "platform") seedRepo := repositories.NewBunOrganizationTeamMemberRepository(db) ctx := context.Background() - _, err := seedRepo.Create(ctx, &types.OrganizationTeamMember{ID: "team-member-1", TeamID: "team-1", MemberID: "member-1"}) + _, err := seedRepo.Create(ctx, &types.OrganizationTeamMember{ID: teamMember1ID, TeamID: team1ID, MemberID: member1ID}) require.NoError(t, err) beforeCalled := false @@ -533,19 +595,19 @@ func TestBunOrganizationTeamMemberRepository_Hooks(t *testing.T) { hooks := &plugintests.MockOrganizationTeamMemberHooks{ BeforeDelete: func(teamMember *types.OrganizationTeamMember) error { beforeCalled = true - require.Equal(t, "team-member-1", teamMember.ID) + require.Equal(t, teamMember1ID, teamMember.ID) return nil }, AfterDelete: func(teamMember types.OrganizationTeamMember) error { afterCalled = true - require.Equal(t, "team-member-1", teamMember.ID) + require.Equal(t, teamMember1ID, teamMember.ID) return nil }, } repo := repositories.NewBunOrganizationTeamMemberRepository(db, hooks) - require.NoError(t, repo.DeleteByTeamIDAndMemberID(ctx, "team-1", "member-1")) - found, err := repo.GetByTeamIDAndMemberID(ctx, "team-1", "member-1") + require.NoError(t, repo.DeleteByTeamIDAndMemberID(ctx, team1ID, member1ID)) + found, err := repo.GetByTeamIDAndMemberID(ctx, team1ID, member1ID) require.NoError(t, err) require.Nil(t, found) require.True(t, beforeCalled) diff --git a/plugins/organizations/repositories/bun_organization_team_repository_test.go b/plugins/organizations/repositories/bun_organization_team_repository_test.go index 574c7366..fbe42d64 100644 --- a/plugins/organizations/repositories/bun_organization_team_repository_test.go +++ b/plugins/organizations/repositories/bun_organization_team_repository_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/uptrace/bun" @@ -15,20 +16,26 @@ import ( func TestBunOrganizationTeamRepository_Create(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + team1ID := uuid.New().String() + team2ID := uuid.New().String() + tests := []struct { name string team *types.OrganizationTeam }{ { name: "keeps provided metadata", - team: &types.OrganizationTeam{ID: "team-1", OrganizationID: "org-1", Name: "Platform", Slug: "platform", Metadata: map[string]any{"tier": "core"}}, + team: &types.OrganizationTeam{ID: team1ID, OrganizationID: org1ID, Name: "Platform", Slug: "platform", Metadata: map[string]any{"tier": "core"}}, }, { name: "keeps metadata and description", team: func() *types.OrganizationTeam { description := new(string) *description = "Core" - return &types.OrganizationTeam{ID: "team-2", OrganizationID: "org-1", Name: "Core", Slug: "core", Description: description, Metadata: map[string]any{"tier": "core"}} + return &types.OrganizationTeam{ID: team2ID, OrganizationID: org1ID, Name: "Core", Slug: "core", Description: description, Metadata: map[string]any{"tier": "core"}} }(), }, } @@ -37,8 +44,8 @@ func TestBunOrganizationTeamRepository_Create(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationTeamRepository(db) created, err := repo.Create(context.Background(), tt.team) require.NoError(t, err) @@ -52,6 +59,11 @@ func TestBunOrganizationTeamRepository_Create(t *testing.T) { func TestBunOrganizationTeamRepository_GetByID(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + team1ID := uuid.New().String() + tests := []struct { name string teamID string @@ -60,26 +72,26 @@ func TestBunOrganizationTeamRepository_GetByID(t *testing.T) { }{ { name: "found", - teamID: "team-1", + teamID: team1ID, expectFound: true, setup: func(t *testing.T) (repositories.OrganizationTeamRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationTeamRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationTeam{ID: "team-1", OrganizationID: "org-1", Name: "Platform", Slug: "platform"}) + _, err := repo.Create(ctx, &types.OrganizationTeam{ID: team1ID, OrganizationID: org1ID, Name: "Platform", Slug: "platform"}) require.NoError(t, err) return repo, ctx }, }, { name: "not found", - teamID: "missing", + teamID: "00000000-0000-0000-0000-000000000000", setup: func(t *testing.T) (repositories.OrganizationTeamRepository, context.Context) { t.Helper() - return repositories.NewBunOrganizationTeamRepository(plugintests.SetupRepoDB(t)), context.Background() + return repositories.NewBunOrganizationTeamRepository(plugintests.SetupRepoDB(t, user1ID, user2ID)), context.Background() }, }, } @@ -94,7 +106,7 @@ func TestBunOrganizationTeamRepository_GetByID(t *testing.T) { require.NoError(t, err) if tt.expectFound { require.NotNil(t, found) - require.Equal(t, "team-1", found.ID) + require.Equal(t, team1ID, found.ID) return } require.Nil(t, found) @@ -105,6 +117,11 @@ func TestBunOrganizationTeamRepository_GetByID(t *testing.T) { func TestBunOrganizationTeamRepository_GetByOrganizationIDAndSlug(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + team1ID := uuid.New().String() + tests := []struct { name string organizationID string @@ -114,28 +131,28 @@ func TestBunOrganizationTeamRepository_GetByOrganizationIDAndSlug(t *testing.T) }{ { name: "found", - organizationID: "org-1", + organizationID: org1ID, slug: "platform", expectFound: true, setup: func(t *testing.T) (repositories.OrganizationTeamRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationTeamRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationTeam{ID: "team-1", OrganizationID: "org-1", Name: "Platform", Slug: "platform"}) + _, err := repo.Create(ctx, &types.OrganizationTeam{ID: team1ID, OrganizationID: org1ID, Name: "Platform", Slug: "platform"}) require.NoError(t, err) return repo, ctx }, }, { name: "not found", - organizationID: "org-1", + organizationID: org1ID, slug: "missing", setup: func(t *testing.T) (repositories.OrganizationTeamRepository, context.Context) { t.Helper() - return repositories.NewBunOrganizationTeamRepository(plugintests.SetupRepoDB(t)), context.Background() + return repositories.NewBunOrganizationTeamRepository(plugintests.SetupRepoDB(t, user1ID, user2ID)), context.Background() }, }, } @@ -150,7 +167,7 @@ func TestBunOrganizationTeamRepository_GetByOrganizationIDAndSlug(t *testing.T) require.NoError(t, err) if tt.expectFound { require.NotNil(t, found) - require.Equal(t, "team-1", found.ID) + require.Equal(t, team1ID, found.ID) return } require.Nil(t, found) @@ -161,6 +178,13 @@ func TestBunOrganizationTeamRepository_GetByOrganizationIDAndSlug(t *testing.T) func TestBunOrganizationTeamRepository_GetAllByOrganizationID(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + org2ID := uuid.New().String() + team1ID := uuid.New().String() + team2ID := uuid.New().String() + tests := []struct { name string organizationID string @@ -169,18 +193,18 @@ func TestBunOrganizationTeamRepository_GetAllByOrganizationID(t *testing.T) { }{ { name: "found", - organizationID: "org-1", + organizationID: org1ID, expectCount: 2, setup: func(t *testing.T) (repositories.OrganizationTeamRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationTeamRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationTeam{ID: "team-1", OrganizationID: "org-1", Name: "Platform", Slug: "platform"}) + _, err := repo.Create(ctx, &types.OrganizationTeam{ID: team1ID, OrganizationID: org1ID, Name: "Platform", Slug: "platform"}) require.NoError(t, err) - _, err = repo.Create(ctx, &types.OrganizationTeam{ID: "team-2", OrganizationID: "org-1", Name: "Core", Slug: "core"}) + _, err = repo.Create(ctx, &types.OrganizationTeam{ID: team2ID, OrganizationID: org1ID, Name: "Core", Slug: "core"}) require.NoError(t, err) return repo, ctx @@ -188,11 +212,11 @@ func TestBunOrganizationTeamRepository_GetAllByOrganizationID(t *testing.T) { }, { name: "empty", - organizationID: "org-2", + organizationID: org2ID, expectCount: 0, setup: func(t *testing.T) (repositories.OrganizationTeamRepository, context.Context) { t.Helper() - return repositories.NewBunOrganizationTeamRepository(plugintests.SetupRepoDB(t)), context.Background() + return repositories.NewBunOrganizationTeamRepository(plugintests.SetupRepoDB(t, user1ID, user2ID)), context.Background() }, }, } @@ -212,6 +236,12 @@ func TestBunOrganizationTeamRepository_GetAllByOrganizationID(t *testing.T) { func TestBunOrganizationTeamRepository_Update(t *testing.T) { t.Parallel() + + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + team1ID := uuid.New().String() + tests := []struct { name string setup func(*testing.T) (repositories.OrganizationTeamRepository, context.Context, *types.OrganizationTeam) @@ -220,12 +250,12 @@ func TestBunOrganizationTeamRepository_Update(t *testing.T) { name: "change name and description", setup: func(t *testing.T) (repositories.OrganizationTeamRepository, context.Context, *types.OrganizationTeam) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationTeamRepository(db) ctx := context.Background() - created, err := repo.Create(ctx, &types.OrganizationTeam{ID: "team-1", OrganizationID: "org-1", Name: "Platform", Slug: "platform"}) + created, err := repo.Create(ctx, &types.OrganizationTeam{ID: team1ID, OrganizationID: org1ID, Name: "Platform", Slug: "platform"}) require.NoError(t, err) return repo, ctx, created }, @@ -252,6 +282,12 @@ func TestBunOrganizationTeamRepository_Update(t *testing.T) { func TestBunOrganizationTeamRepository_Delete(t *testing.T) { t.Parallel() + + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + team1ID := uuid.New().String() + tests := []struct { name string teamID string @@ -259,15 +295,15 @@ func TestBunOrganizationTeamRepository_Delete(t *testing.T) { }{ { name: "delete existing", - teamID: "team-1", + teamID: team1ID, setup: func(t *testing.T) (repositories.OrganizationTeamRepository, context.Context) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationTeamRepository(db) ctx := context.Background() - _, err := repo.Create(ctx, &types.OrganizationTeam{ID: "team-1", OrganizationID: "org-1", Name: "Platform", Slug: "platform"}) + _, err := repo.Create(ctx, &types.OrganizationTeam{ID: team1ID, OrganizationID: org1ID, Name: "Platform", Slug: "platform"}) require.NoError(t, err) return repo, ctx }, @@ -291,6 +327,11 @@ func TestBunOrganizationTeamRepository_Delete(t *testing.T) { func TestBunOrganizationTeamRepository_WithTx(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + team1ID := uuid.New().String() + tests := []struct { name string commit bool @@ -301,8 +342,8 @@ func TestBunOrganizationTeamRepository_WithTx(t *testing.T) { commit: true, setup: func(t *testing.T) (repositories.OrganizationTeamRepository, context.Context, repositories.OrganizationTeamRepository, bun.Tx) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationTeamRepository(db) ctx := context.Background() tx, err := db.BeginTx(ctx, nil) @@ -315,8 +356,8 @@ func TestBunOrganizationTeamRepository_WithTx(t *testing.T) { commit: false, setup: func(t *testing.T) (repositories.OrganizationTeamRepository, context.Context, repositories.OrganizationTeamRepository, bun.Tx) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") repo := repositories.NewBunOrganizationTeamRepository(db) ctx := context.Background() tx, err := db.BeginTx(ctx, nil) @@ -334,10 +375,10 @@ func TestBunOrganizationTeamRepository_WithTx(t *testing.T) { require.NotNil(t, txRepo) require.IsType(t, &repositories.BunOrganizationTeamRepository{}, txRepo) - created, err := txRepo.Create(ctx, &types.OrganizationTeam{ID: "team-1", OrganizationID: "org-1", Name: "Platform", Slug: "platform"}) + created, err := txRepo.Create(ctx, &types.OrganizationTeam{ID: team1ID, OrganizationID: org1ID, Name: "Platform", Slug: "platform"}) require.NoError(t, err) require.NotNil(t, created) - require.Equal(t, "team-1", created.ID) + require.Equal(t, team1ID, created.ID) if tt.commit { require.NoError(t, tx.Commit()) @@ -345,7 +386,7 @@ func TestBunOrganizationTeamRepository_WithTx(t *testing.T) { require.NoError(t, tx.Rollback()) } - found, err := repo.GetByID(ctx, "team-1") + found, err := repo.GetByID(ctx, team1ID) require.NoError(t, err) if tt.commit { require.NotNil(t, found) @@ -359,6 +400,11 @@ func TestBunOrganizationTeamRepository_WithTx(t *testing.T) { func TestBunOrganizationTeamRepository_Hooks(t *testing.T) { t.Parallel() + user1ID := uuid.New().String() + user2ID := uuid.New().String() + org1ID := uuid.New().String() + team1ID := uuid.New().String() + tests := []struct { name string run func(*testing.T) @@ -367,27 +413,27 @@ func TestBunOrganizationTeamRepository_Hooks(t *testing.T) { name: "create hooks", run: func(t *testing.T) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") beforeCalled := false afterCalled := false hooks := &plugintests.MockOrganizationTeamHooks{ BeforeCreate: func(team *types.OrganizationTeam) error { beforeCalled = true - require.Equal(t, "team-1", team.ID) + require.Equal(t, team1ID, team.ID) require.Equal(t, "Platform", team.Name) return nil }, AfterCreate: func(team types.OrganizationTeam) error { afterCalled = true - require.Equal(t, "team-1", team.ID) + require.Equal(t, team1ID, team.ID) return nil }, } repo := repositories.NewBunOrganizationTeamRepository(db, hooks) - created, err := repo.Create(context.Background(), &types.OrganizationTeam{ID: "team-1", OrganizationID: "org-1", Name: "Platform", Slug: "platform"}) + created, err := repo.Create(context.Background(), &types.OrganizationTeam{ID: team1ID, OrganizationID: org1ID, Name: "Platform", Slug: "platform"}) require.NoError(t, err) require.NotNil(t, created) require.True(t, beforeCalled) @@ -398,12 +444,12 @@ func TestBunOrganizationTeamRepository_Hooks(t *testing.T) { name: "update hooks", run: func(t *testing.T) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") seedRepo := repositories.NewBunOrganizationTeamRepository(db) ctx := context.Background() - team, err := seedRepo.Create(ctx, &types.OrganizationTeam{ID: "team-1", OrganizationID: "org-1", Name: "Platform", Slug: "platform"}) + team, err := seedRepo.Create(ctx, &types.OrganizationTeam{ID: team1ID, OrganizationID: org1ID, Name: "Platform", Slug: "platform"}) require.NoError(t, err) beforeCalled := false @@ -437,12 +483,12 @@ func TestBunOrganizationTeamRepository_Hooks(t *testing.T) { name: "delete hooks", run: func(t *testing.T) { t.Helper() - db := plugintests.SetupRepoDB(t) - plugintests.SeedOrganization(t, db, "org-1", "user-1", "Acme Inc", "acme-inc") + db := plugintests.SetupRepoDB(t, user1ID, user2ID) + plugintests.SeedOrganization(t, db, org1ID, user1ID, "Acme Inc", "acme-inc") seedRepo := repositories.NewBunOrganizationTeamRepository(db) ctx := context.Background() - _, err := seedRepo.Create(ctx, &types.OrganizationTeam{ID: "team-1", OrganizationID: "org-1", Name: "Platform", Slug: "platform"}) + _, err := seedRepo.Create(ctx, &types.OrganizationTeam{ID: team1ID, OrganizationID: org1ID, Name: "Platform", Slug: "platform"}) require.NoError(t, err) beforeCalled := false @@ -450,19 +496,19 @@ func TestBunOrganizationTeamRepository_Hooks(t *testing.T) { hooks := &plugintests.MockOrganizationTeamHooks{ BeforeDelete: func(team *types.OrganizationTeam) error { beforeCalled = true - require.Equal(t, "team-1", team.ID) + require.Equal(t, team1ID, team.ID) return nil }, AfterDelete: func(team types.OrganizationTeam) error { afterCalled = true - require.Equal(t, "team-1", team.ID) + require.Equal(t, team1ID, team.ID) return nil }, } repo := repositories.NewBunOrganizationTeamRepository(db, hooks) - require.NoError(t, repo.Delete(ctx, "team-1")) - found, err := repo.GetByID(ctx, "team-1") + require.NoError(t, repo.Delete(ctx, team1ID)) + found, err := repo.GetByID(ctx, team1ID) require.NoError(t, err) require.Nil(t, found) require.True(t, beforeCalled) diff --git a/plugins/organizations/tests/repositories.go b/plugins/organizations/tests/repositories.go index 0440322d..a239f4ae 100644 --- a/plugins/organizations/tests/repositories.go +++ b/plugins/organizations/tests/repositories.go @@ -4,7 +4,6 @@ import ( "context" "reflect" - _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/mock" "github.com/uptrace/bun" diff --git a/plugins/organizations/tests/test_helpers.go b/plugins/organizations/tests/test_helpers.go index 08a4ab43..c4c9c637 100644 --- a/plugins/organizations/tests/test_helpers.go +++ b/plugins/organizations/tests/test_helpers.go @@ -2,14 +2,11 @@ package tests import ( "context" - "database/sql" "testing" "time" - _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/require" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/sqlitedialect" internaltests "github.com/Authula/authula/internal/tests" "github.com/Authula/authula/migrations" @@ -21,35 +18,29 @@ func Actor(userID string) *models.Actor { return &models.Actor{ID: userID, Type: models.ActorUser} } -func SetupRepoDB(t *testing.T) *bun.DB { +func SetupRepoDB(t *testing.T, user1ID, user2ID string) *bun.DB { t.Helper() - sqlDB, err := sql.Open("sqlite3", ":memory:") - require.NoError(t, err) - sqlDB.SetMaxOpenConns(1) - t.Cleanup(func() { _ = sqlDB.Close() }) - - db := bun.NewDB(sqlDB, sqlitedialect.New()) - t.Cleanup(func() { _ = db.Close() }) + db := internaltests.NewIntegrationTestDB(t) ctx := context.Background() migrator, err := migrations.NewMigrator(db, &internaltests.MockLogger{}) require.NoError(t, err) - coreSet, err := migrations.CoreMigrationSet("sqlite") + coreSet, err := migrations.CoreMigrationSet() require.NoError(t, err) orgSet := migrations.MigrationSet{ PluginID: models.PluginOrganizations.String(), DependsOn: []string{migrations.CorePluginID}, - Migrations: orgpluginmigrations.ForProvider("sqlite"), + Migrations: orgpluginmigrations.Migrations(), } err = migrator.Migrate(ctx, []migrations.MigrationSet{coreSet, orgSet}) require.NoError(t, err) - SeedUser(t, db, "user-1") - SeedUser(t, db, "user-2") + SeedUser(t, db, user1ID) + SeedUser(t, db, user2ID) return db } diff --git a/plugins/rate-limit/migrations.go b/plugins/rate-limit/migrations.go index b38d6083..e7ca5117 100644 --- a/plugins/rate-limit/migrations.go +++ b/plugins/rate-limit/migrations.go @@ -5,6 +5,6 @@ import ( "github.com/Authula/authula/plugins/rate-limit/migrationset" ) -func ratelimitMigrationsForProvider(provider string) []migrations.Migration { - return migrationset.ForProvider(provider) +func ratelimitMigrations() []migrations.Migration { + return migrationset.Migrations() } diff --git a/plugins/rate-limit/migrationset/migrations.go b/plugins/rate-limit/migrationset/migrations.go index 9a65082c..957dec21 100644 --- a/plugins/rate-limit/migrationset/migrations.go +++ b/plugins/rate-limit/migrationset/migrations.go @@ -8,57 +8,8 @@ import ( "github.com/Authula/authula/migrations" ) -func ForProvider(provider string) []migrations.Migration { - return migrations.ForProvider(provider, migrations.ProviderVariants{ - "sqlite": func() []migrations.Migration { return []migrations.Migration{sqliteInitial(), sqliteRules()} }, - "postgres": func() []migrations.Migration { return []migrations.Migration{postgresInitial(), postgresRules()} }, - "mysql": func() []migrations.Migration { return []migrations.Migration{mysqlInitial(), mysqlRules()} }, - }) -} - -func sqliteInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260130000000_rate_limit_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - // ----------------------------------- - `PRAGMA foreign_keys = ON;`, - // ----------------------------------- - `CREATE TABLE IF NOT EXISTS rate_limits ( - key TEXT NOT NULL PRIMARY KEY, - count INTEGER NOT NULL, - expires_at DATETIME NOT NULL - );`, - `CREATE INDEX IF NOT EXISTS idx_rate_limits_expires_at ON rate_limits(expires_at);`, - // ----------------------------------- - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements(ctx, tx, `DROP TABLE IF EXISTS rate_limits;`) - }, - } -} - -func sqliteRules() migrations.Migration { - return migrations.Migration{ - Version: "20260130000001_rate_limit_rules", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS rate_limit_rules ( - key TEXT NOT NULL PRIMARY KEY, - window_seconds INTEGER NOT NULL, - max_requests INTEGER NOT NULL - );`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements(ctx, tx, `DROP TABLE IF EXISTS rate_limit_rules;`) - }, - } +func Migrations() []migrations.Migration { + return []migrations.Migration{postgresInitial(), postgresRules()} } func postgresInitial() migrations.Migration { @@ -101,43 +52,3 @@ func postgresRules() migrations.Migration { }, } } - -func mysqlInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260130000000_rate_limit_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS rate_limits ( - key TEXT NOT NULL PRIMARY KEY, - count INTEGER NOT NULL, - expires_at TIMESTAMP NOT NULL - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements(ctx, tx, `DROP TABLE IF EXISTS rate_limits;`) - }, - } -} - -func mysqlRules() migrations.Migration { - return migrations.Migration{ - Version: "20260130000001_rate_limit_rules", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS rate_limit_rules ( - key TEXT NOT NULL PRIMARY KEY, - window_seconds INTEGER NOT NULL, - max_requests INTEGER NOT NULL - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements(ctx, tx, `DROP TABLE IF EXISTS rate_limit_rules;`) - }, - } -} diff --git a/plugins/rate-limit/plugin.go b/plugins/rate-limit/plugin.go index 75b4f14f..d16c36ae 100644 --- a/plugins/rate-limit/plugin.go +++ b/plugins/rate-limit/plugin.go @@ -73,11 +73,11 @@ func (p *RateLimitPlugin) trySecondaryStorage() types.RateLimitProvider { return provider } -func (p *RateLimitPlugin) Migrations(provider string) []migrations.Migration { +func (p *RateLimitPlugin) Migrations() []migrations.Migration { if p.pluginConfig.Provider != types.RateLimitProviderDatabase { return nil } - return ratelimitMigrationsForProvider(provider) + return ratelimitMigrations() } func (p *RateLimitPlugin) DependsOn() []string { diff --git a/plugins/rate-limit/repositories/bun_rate_limit_repository_test.go b/plugins/rate-limit/repositories/bun_rate_limit_repository_test.go index 78dab3ff..88a861be 100644 --- a/plugins/rate-limit/repositories/bun_rate_limit_repository_test.go +++ b/plugins/rate-limit/repositories/bun_rate_limit_repository_test.go @@ -46,7 +46,7 @@ func TestBunRateLimitRepository_GetByKey(t *testing.T) { t.Parallel() ctx := context.Background() - db := tests.NewSQLiteIntegrationDB(t) + db := tests.NewIntegrationTestDB(t) repo := NewRateLimitRepository(db) require.NoError(t, db.ResetModel(ctx, (*types.RateLimit)(nil))) defer func() { require.NoError(t, db.Close()) }() @@ -107,7 +107,7 @@ func TestBunRateLimitRepository_UpdateOrCreate(t *testing.T) { t.Parallel() ctx := context.Background() - db := tests.NewSQLiteIntegrationDB(t) + db := tests.NewIntegrationTestDB(t) repo := NewRateLimitRepository(db) require.NoError(t, db.ResetModel(ctx, (*types.RateLimit)(nil))) defer func() { require.NoError(t, db.Close()) }() @@ -169,7 +169,7 @@ func TestBunRateLimitRepository_CleanupExpired(t *testing.T) { t.Parallel() ctx := context.Background() - db := tests.NewSQLiteIntegrationDB(t) + db := tests.NewIntegrationTestDB(t) repo := NewRateLimitRepository(db) require.NoError(t, db.ResetModel(ctx, (*types.RateLimit)(nil))) defer func() { require.NoError(t, db.Close()) }() @@ -213,7 +213,7 @@ func TestBunRateLimitRepository_SetRule(t *testing.T) { t.Parallel() ctx := context.Background() - db := tests.NewSQLiteIntegrationDB(t) + db := tests.NewIntegrationTestDB(t) repo := NewRateLimitRepository(db) require.NoError(t, db.ResetModel(ctx, (*types.RateLimitRuleRecord)(nil))) defer func() { require.NoError(t, db.Close()) }() @@ -253,7 +253,7 @@ func TestBunRateLimitRepository_GetRule(t *testing.T) { t.Parallel() ctx := context.Background() - db := tests.NewSQLiteIntegrationDB(t) + db := tests.NewIntegrationTestDB(t) repo := NewRateLimitRepository(db) require.NoError(t, db.ResetModel(ctx, (*types.RateLimitRuleRecord)(nil))) defer func() { require.NoError(t, db.Close()) }() @@ -309,7 +309,7 @@ func TestBunRateLimitRepository_DeleteRule(t *testing.T) { t.Parallel() ctx := context.Background() - db := tests.NewSQLiteIntegrationDB(t) + db := tests.NewIntegrationTestDB(t) repo := NewRateLimitRepository(db) require.NoError(t, db.ResetModel(ctx, (*types.RateLimitRuleRecord)(nil))) defer func() { require.NoError(t, db.Close()) }() diff --git a/plugins/rate-limit/services/database_provider_test.go b/plugins/rate-limit/services/database_provider_test.go index d1d1c883..fab47470 100644 --- a/plugins/rate-limit/services/database_provider_test.go +++ b/plugins/rate-limit/services/database_provider_test.go @@ -13,7 +13,7 @@ import ( func TestDatabaseProviderRuleLifecycle(t *testing.T) { t.Parallel() - db := tests.NewSQLiteIntegrationDB(t) + db := tests.NewIntegrationTestDB(t) provider, err := NewDatabaseProviderWithConfig(db, types.DatabaseStorageConfig{}) require.NoError(t, err) diff --git a/plugins/secondary-storage/database_secondary_storage_test.go b/plugins/secondary-storage/database_secondary_storage_test.go index 39267478..c633e48a 100644 --- a/plugins/secondary-storage/database_secondary_storage_test.go +++ b/plugins/secondary-storage/database_secondary_storage_test.go @@ -2,25 +2,19 @@ package secondarystorage import ( "context" - "database/sql" "errors" "strconv" "testing" "time" - _ "github.com/mattn/go-sqlite3" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/sqlitedialect" + + "github.com/Authula/authula/internal/tests" ) -// Helper function to create an in-memory SQLite database for testing +// Helper function to create a test database func newTestDB(t *testing.T) bun.IDB { - sqldb, err := sql.Open("sqlite3", ":memory:") - if err != nil { - t.Fatalf("failed to create test database: %v", err) - } - - db := bun.NewDB(sqldb, sqlitedialect.New()) + db := tests.NewIntegrationTestDB(t) // Create the KeyValueStore table ctx := context.Background() @@ -402,18 +396,7 @@ func TestDatabaseStorage_MultipleKeys(t *testing.T) { } func TestDatabaseStorage_ConcurrentReads(t *testing.T) { - // Use file-based SQLite for concurrent tests as :memory: doesn't handle concurrency well - sqldb, err := sql.Open("sqlite3", "file::memory:?cache=shared") - if err != nil { - t.Fatalf("failed to create test database: %v", err) - } - - db := bun.NewDB(sqldb, sqlitedialect.New()) - - ctx := context.Background() - if _, err := db.NewCreateTable().Model(&KeyValueStore{}).Exec(ctx); err != nil { - t.Fatalf("failed to create KeyValueStore table: %v", err) - } + db := newTestDB(t) storage := newTestDatabaseStorage(t, db) defer func() { @@ -422,7 +405,7 @@ func TestDatabaseStorage_ConcurrentReads(t *testing.T) { } }() - ctx = context.Background() + ctx := context.Background() key := "concurrent_key" value := "concurrent_value" @@ -687,20 +670,9 @@ func TestDatabaseStorage_DifferentValueTypes(t *testing.T) { } func TestDatabaseStorage_PersistenceAcrossInstances(t *testing.T) { - // Create a persistent DB (not in-memory) - sqldb, err := sql.Open("sqlite3", ":memory:") - if err != nil { - t.Fatalf("failed to create test database: %v", err) - } - - db := bun.NewDB(sqldb, sqlitedialect.New()) + db := newTestDB(t) ctx := context.Background() - if _, err := db.NewCreateTable().Model(&KeyValueStore{}).Exec(ctx); err != nil { - t.Fatalf("failed to create KeyValueStore table: %v", err) - } - - ctx = context.Background() // Create first storage instance and set values storage1 := newTestDatabaseStorage(t, db) diff --git a/plugins/secondary-storage/migrations.go b/plugins/secondary-storage/migrations.go index 85d161c6..1b91810f 100644 --- a/plugins/secondary-storage/migrations.go +++ b/plugins/secondary-storage/migrations.go @@ -8,96 +8,44 @@ import ( "github.com/Authula/authula/migrations" ) -func secondaryStorageMigrationsForProvider(provider string) []migrations.Migration { - return migrations.ForProvider(provider, migrations.ProviderVariants{ - "sqlite": func() []migrations.Migration { return []migrations.Migration{secondaryStorageSQLiteInitial()} }, - "postgres": func() []migrations.Migration { return []migrations.Migration{secondaryStoragePostgresInitial()} }, - "mysql": func() []migrations.Migration { return []migrations.Migration{secondaryStorageMySQLInitial()} }, - }) -} - -func secondaryStorageSQLiteInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260129000000_secondary_storage_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS key_value_store ( - key VARCHAR(255) PRIMARY KEY, - value TEXT NOT NULL, - expires_at TIMESTAMP, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -);`, - `CREATE INDEX IF NOT EXISTS idx_key_value_store_expires_at ON key_value_store(expires_at);`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements(ctx, tx, `DROP TABLE IF EXISTS key_value_store;`) - }, - } -} - -func secondaryStoragePostgresInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260129000000_secondary_storage_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE OR REPLACE FUNCTION key_value_store_update_updated_at_func() +func secondaryStorageMigrations() []migrations.Migration { + return []migrations.Migration{ + { + Version: "20260129000000_secondary_storage_initial", + Up: func(ctx context.Context, tx bun.Tx) error { + return migrations.ExecStatements( + ctx, + tx, + `CREATE OR REPLACE FUNCTION key_value_store_update_updated_at_func() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql;`, - `CREATE TABLE IF NOT EXISTS key_value_store ( + `CREATE TABLE IF NOT EXISTS key_value_store ( key VARCHAR(255) PRIMARY KEY, value TEXT NOT NULL, expires_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() );`, - `CREATE INDEX IF NOT EXISTS idx_key_value_store_expires_at ON key_value_store(expires_at);`, - `DROP TRIGGER IF EXISTS key_value_store_update_updated_at_trigger ON key_value_store;`, - `CREATE TRIGGER key_value_store_update_updated_at_trigger + `CREATE INDEX IF NOT EXISTS idx_key_value_store_expires_at ON key_value_store(expires_at);`, + `DROP TRIGGER IF EXISTS key_value_store_update_updated_at_trigger ON key_value_store;`, + `CREATE TRIGGER key_value_store_update_updated_at_trigger BEFORE UPDATE ON key_value_store FOR EACH ROW EXECUTE FUNCTION key_value_store_update_updated_at_func();`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TRIGGER IF EXISTS key_value_store_update_updated_at_trigger ON key_value_store;`, - `DROP TABLE IF EXISTS key_value_store;`, - ) - }, - } -} - -func secondaryStorageMySQLInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260129000000_secondary_storage_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS key_value_store ( - key VARCHAR(255) PRIMARY KEY, - value LONGTEXT NOT NULL, - expires_at TIMESTAMP NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - INDEX idx_key_value_store_expires_at (expires_at) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements(ctx, tx, `DROP TABLE IF EXISTS key_value_store;`) + ) + }, + Down: func(ctx context.Context, tx bun.Tx) error { + return migrations.ExecStatements( + ctx, + tx, + `DROP TRIGGER IF EXISTS key_value_store_update_updated_at_trigger ON key_value_store;`, + `DROP TABLE IF EXISTS key_value_store;`, + ) + }, }, } } diff --git a/plugins/secondary-storage/plugin.go b/plugins/secondary-storage/plugin.go index a1674ce2..fac074cc 100644 --- a/plugins/secondary-storage/plugin.go +++ b/plugins/secondary-storage/plugin.go @@ -153,11 +153,11 @@ func (p *SecondaryStoragePlugin) initRedisProvider(ctx *models.PluginContext) (m }) } -func (p *SecondaryStoragePlugin) Migrations(provider string) []migrations.Migration { +func (p *SecondaryStoragePlugin) Migrations() []migrations.Migration { if p.config.Provider != SecondaryStorageProviderDatabase { return nil } - return secondaryStorageMigrationsForProvider(provider) + return secondaryStorageMigrations() } func (p *SecondaryStoragePlugin) DependsOn() []string { diff --git a/plugins/totp/migrations.go b/plugins/totp/migrations.go index 43a91c9a..856ce38b 100644 --- a/plugins/totp/migrations.go +++ b/plugins/totp/migrations.go @@ -11,79 +11,29 @@ import ( // MigrationSet returns the TOTP plugin migrations as a migration set // compatible with the shared migrator. -func MigrationSet(provider string) migrations.MigrationSet { +func MigrationSet() migrations.MigrationSet { return migrations.MigrationSet{ PluginID: models.PluginTOTP.String(), - Migrations: totpMigrationsForProvider(provider), + Migrations: totpMigrations(), } } -func totpMigrationsForProvider(provider string) []migrations.Migration { - return migrations.ForProvider(provider, migrations.ProviderVariants{ - "sqlite": func() []migrations.Migration { return []migrations.Migration{totpSQLiteInitial()} }, - "postgres": func() []migrations.Migration { return []migrations.Migration{totpPostgresInitial()} }, - "mysql": func() []migrations.Migration { return []migrations.Migration{totpMySQLInitial()} }, - }) -} - -func totpSQLiteInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260318000000_totp_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS totp ( - id VARCHAR(36) PRIMARY KEY, - user_id VARCHAR(36) NOT NULL, - secret TEXT NOT NULL, - backup_codes TEXT NOT NULL, - enabled BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE - );`, - `CREATE UNIQUE INDEX IF NOT EXISTS idx_totp_user_id ON totp(user_id);`, - `CREATE TABLE IF NOT EXISTS trusted_devices ( - id VARCHAR(36) PRIMARY KEY, - user_id VARCHAR(36) NOT NULL, - token VARCHAR(64) NOT NULL, - user_agent TEXT NOT NULL DEFAULT '', - expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE - );`, - `CREATE INDEX IF NOT EXISTS idx_trusted_devices_user_id ON trusted_devices(user_id);`, - `CREATE INDEX IF NOT EXISTS idx_trusted_devices_token ON trusted_devices(token);`, - `CREATE INDEX IF NOT EXISTS idx_trusted_devices_expires_at ON trusted_devices(expires_at);`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS trusted_devices;`, - `DROP TABLE IF EXISTS totp;`, - ) - }, - } -} - -func totpPostgresInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260318000000_totp_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE OR REPLACE FUNCTION totp_update_updated_at_func() +func totpMigrations() []migrations.Migration { + return []migrations.Migration{ + { + Version: "20260318000000_totp_initial", + Up: func(ctx context.Context, tx bun.Tx) error { + return migrations.ExecStatements( + ctx, + tx, + `CREATE OR REPLACE FUNCTION totp_update_updated_at_func() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql;`, - `CREATE TABLE IF NOT EXISTS totp ( + `CREATE TABLE IF NOT EXISTS totp ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, secret TEXT NOT NULL, @@ -92,13 +42,13 @@ func totpPostgresInitial() migrations.Migration { created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() );`, - `CREATE UNIQUE INDEX IF NOT EXISTS idx_totp_user_id ON totp(user_id);`, - `DROP TRIGGER IF EXISTS totp_update_updated_at_trigger ON totp;`, - `CREATE TRIGGER totp_update_updated_at_trigger + `CREATE UNIQUE INDEX IF NOT EXISTS idx_totp_user_id ON totp(user_id);`, + `DROP TRIGGER IF EXISTS totp_update_updated_at_trigger ON totp;`, + `CREATE TRIGGER totp_update_updated_at_trigger BEFORE UPDATE ON totp FOR EACH ROW EXECUTE FUNCTION totp_update_updated_at_func();`, - `CREATE TABLE IF NOT EXISTS trusted_devices ( + `CREATE TABLE IF NOT EXISTS trusted_devices ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token VARCHAR(64) NOT NULL, @@ -106,63 +56,21 @@ func totpPostgresInitial() migrations.Migration { expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() );`, - `CREATE INDEX IF NOT EXISTS idx_trusted_devices_user_id ON trusted_devices(user_id);`, - `CREATE INDEX IF NOT EXISTS idx_trusted_devices_token ON trusted_devices(token);`, - `CREATE INDEX IF NOT EXISTS idx_trusted_devices_expires_at ON trusted_devices(expires_at);`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TRIGGER IF EXISTS totp_update_updated_at_trigger ON totp;`, - `DROP FUNCTION IF EXISTS totp_update_updated_at_func();`, - `DROP TABLE IF EXISTS trusted_devices;`, - `DROP TABLE IF EXISTS totp;`, - ) - }, - } -} - -func totpMySQLInitial() migrations.Migration { - return migrations.Migration{ - Version: "20260318000000_totp_initial", - Up: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `CREATE TABLE IF NOT EXISTS totp ( - id BINARY(16) NOT NULL PRIMARY KEY, - user_id BINARY(16) NOT NULL, - secret TEXT NOT NULL, - backup_codes TEXT NOT NULL, - enabled BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - UNIQUE INDEX idx_totp_user_id (user_id) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - `CREATE TABLE IF NOT EXISTS trusted_devices ( - id BINARY(16) NOT NULL PRIMARY KEY, - user_id BINARY(16) NOT NULL, - token VARCHAR(255) NOT NULL, - user_agent TEXT NOT NULL, - expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - INDEX idx_trusted_devices_user_id (user_id), - INDEX idx_trusted_devices_token (token), - INDEX idx_trusted_devices_expires_at (expires_at) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`, - ) - }, - Down: func(ctx context.Context, tx bun.Tx) error { - return migrations.ExecStatements( - ctx, - tx, - `DROP TABLE IF EXISTS trusted_devices;`, - `DROP TABLE IF EXISTS totp;`, - ) + `CREATE INDEX IF NOT EXISTS idx_trusted_devices_user_id ON trusted_devices(user_id);`, + `CREATE INDEX IF NOT EXISTS idx_trusted_devices_token ON trusted_devices(token);`, + `CREATE INDEX IF NOT EXISTS idx_trusted_devices_expires_at ON trusted_devices(expires_at);`, + ) + }, + Down: func(ctx context.Context, tx bun.Tx) error { + return migrations.ExecStatements( + ctx, + tx, + `DROP TRIGGER IF EXISTS totp_update_updated_at_trigger ON totp;`, + `DROP FUNCTION IF EXISTS totp_update_updated_at_func();`, + `DROP TABLE IF EXISTS trusted_devices;`, + `DROP TABLE IF EXISTS totp;`, + ) + }, }, } } diff --git a/plugins/totp/plugin.go b/plugins/totp/plugin.go index 653bde84..a7413586 100644 --- a/plugins/totp/plugin.go +++ b/plugins/totp/plugin.go @@ -136,8 +136,8 @@ func (p *TOTPPlugin) Hooks() []models.Hook { return p.buildHooks() } -func (p *TOTPPlugin) Migrations(provider string) []migrations.Migration { - return totpMigrationsForProvider(provider) +func (p *TOTPPlugin) Migrations() []migrations.Migration { + return totpMigrations() } func (p *TOTPPlugin) DependsOn() []string { diff --git a/plugins/totp/plugin_test.go b/plugins/totp/plugin_test.go index 4187987a..5434a0ef 100644 --- a/plugins/totp/plugin_test.go +++ b/plugins/totp/plugin_test.go @@ -2,18 +2,16 @@ package totp import ( "context" - "database/sql" "net/http" "net/http/httptest" "testing" "time" - _ "github.com/mattn/go-sqlite3" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/sqlitedialect" "github.com/Authula/authula/internal/tests" "github.com/Authula/authula/migrations" @@ -76,20 +74,15 @@ func buildTestPlugin(t *testing.T) (*TOTPPlugin, *tests.MockUserService, *tests. func newHookTestDB(t *testing.T) *bun.DB { t.Helper() - sqlDB, err := sql.Open("sqlite3", ":memory:") - require.NoError(t, err) - t.Cleanup(func() { _ = sqlDB.Close() }) - - db := bun.NewDB(sqlDB, sqlitedialect.New()) - t.Cleanup(func() { _ = db.Close() }) + db := tests.NewIntegrationTestDB(t) ctx := context.Background() migrator, err := migrations.NewMigrator(db, &tests.MockLogger{}) require.NoError(t, err) - coreSet, err := migrations.CoreMigrationSet("sqlite") + coreSet, err := migrations.CoreMigrationSet() require.NoError(t, err) - totpSet := MigrationSet("sqlite") + totpSet := MigrationSet() err = migrator.Migrate(ctx, []migrations.MigrationSet{coreSet, totpSet}) require.NoError(t, err) @@ -108,6 +101,9 @@ func findPendingCookieValue(w *httptest.ResponseRecorder) (string, bool) { } func TestInterceptHook(t *testing.T) { + userID1 := uuid.New().String() + userID2 := uuid.New().String() + cases := []struct { name string arrange func(t *testing.T, plugin *TOTPPlugin, db *bun.DB, totpRepo *repository.TOTPRepository, tokenSvc *tests.MockTokenService, verifSvc *tests.MockVerificationService) @@ -119,18 +115,18 @@ func TestInterceptHook(t *testing.T) { t.Helper() ctx := context.Background() - _, err := db.ExecContext(ctx, `INSERT INTO users (id, name, email) VALUES (?, ?, ?)`, "user-1", "User One", "user1@example.com") + _, err := db.ExecContext(ctx, `INSERT INTO users (id, name, email) VALUES (?, ?, ?)`, userID1, "User One", "user1@example.com") require.NoError(t, err) - _, err = db.ExecContext(ctx, `INSERT INTO users (id, name, email) VALUES (?, ?, ?)`, "user-2", "User Two", "user2@example.com") + _, err = db.ExecContext(ctx, `INSERT INTO users (id, name, email) VALUES (?, ?, ?)`, userID2, "User Two", "user2@example.com") require.NoError(t, err) plugin.totpRepo = totpRepo - _, err = totpRepo.Create(ctx, "user-1", "enc-secret", "[]") + _, err = totpRepo.Create(ctx, userID1, "enc-secret", "[]") require.NoError(t, err) - require.NoError(t, totpRepo.SetEnabled(ctx, "user-1", true)) + require.NoError(t, totpRepo.SetEnabled(ctx, userID1, true)) - _, err = totpRepo.CreateTrustedDevice(ctx, "user-2", "hash-trusted", "ua", time.Now().UTC().Add(24*time.Hour)) + _, err = totpRepo.CreateTrustedDevice(ctx, userID2, "hash-trusted", "ua", time.Now().UTC().Add(24*time.Hour)) require.NoError(t, err) tokenSvc.On("Hash", "trusted-cookie-token").Return("hash-trusted").Once() @@ -139,10 +135,10 @@ func TestInterceptHook(t *testing.T) { verifSvc.On( "Create", mock.Anything, - "user-1", + userID1, "hash-pending", models.TypeTOTPPendingAuth, - "user-1", + userID1, plugin.pluginConfig.PendingTokenExpiry, ).Return(&models.Verification{ID: "verif-1"}, nil).Once() }, @@ -164,16 +160,16 @@ func TestInterceptHook(t *testing.T) { t.Helper() ctx := context.Background() - _, err := db.ExecContext(ctx, `INSERT INTO users (id, name, email) VALUES (?, ?, ?)`, "user-1", "User One", "user1@example.com") + _, err := db.ExecContext(ctx, `INSERT INTO users (id, name, email) VALUES (?, ?, ?)`, userID1, "User One", "user1@example.com") require.NoError(t, err) plugin.totpRepo = totpRepo - _, err = totpRepo.Create(ctx, "user-1", "enc-secret", "[]") + _, err = totpRepo.Create(ctx, userID1, "enc-secret", "[]") require.NoError(t, err) - require.NoError(t, totpRepo.SetEnabled(ctx, "user-1", true)) + require.NoError(t, totpRepo.SetEnabled(ctx, userID1, true)) - _, err = totpRepo.CreateTrustedDevice(ctx, "user-1", "hash-trusted", "ua", time.Now().UTC().Add(24*time.Hour)) + _, err = totpRepo.CreateTrustedDevice(ctx, userID1, "hash-trusted", "ua", time.Now().UTC().Add(24*time.Hour)) require.NoError(t, err) tokenSvc.On("Hash", "trusted-cookie-token").Return("hash-trusted").Once() @@ -202,7 +198,7 @@ func TestInterceptHook(t *testing.T) { req.AddCookie(&http.Cookie{Name: constants.CookieTOTPTrusted, Value: "trusted-cookie-token"}) w := httptest.NewRecorder() - userID := "user-1" + userID := userID1 reqCtx := &models.RequestContext{ Request: req, ResponseWriter: w, diff --git a/plugins/totp/repository/totp_repository_test.go b/plugins/totp/repository/totp_repository_test.go index 28a99cfb..fa1ef72a 100644 --- a/plugins/totp/repository/totp_repository_test.go +++ b/plugins/totp/repository/totp_repository_test.go @@ -2,14 +2,12 @@ package repository_test import ( "context" - "database/sql" "testing" "time" - _ "github.com/mattn/go-sqlite3" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/sqlitedialect" internaltests "github.com/Authula/authula/internal/tests" "github.com/Authula/authula/migrations" @@ -21,20 +19,15 @@ import ( func newTestTOTPDB(t *testing.T) *bun.DB { t.Helper() - sqlDB, err := sql.Open("sqlite3", ":memory:") - require.NoError(t, err) - t.Cleanup(func() { _ = sqlDB.Close() }) - - db := bun.NewDB(sqlDB, sqlitedialect.New()) - t.Cleanup(func() { _ = db.Close() }) + db := internaltests.NewIntegrationTestDB(t) ctx := context.Background() migrator, err := migrations.NewMigrator(db, &internaltests.MockLogger{}) require.NoError(t, err) - coreSet, err := migrations.CoreMigrationSet("sqlite") + coreSet, err := migrations.CoreMigrationSet() require.NoError(t, err) - totpSet := totpplugin.MigrationSet("sqlite") + totpSet := totpplugin.MigrationSet() err = migrator.Migrate(ctx, []migrations.MigrationSet{coreSet, totpSet}) require.NoError(t, err) @@ -87,20 +80,21 @@ func TestWithTx(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-with-tx-1") - createTestTOTPRecord(t, ctx, repo, "user-with-tx-1") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) tx, err := db.BeginTx(ctx, nil) require.NoError(t, err) txRepo := repo.WithTx(tx) - err = txRepo.UpdateBackupCodes(ctx, "user-with-tx-1", `["h2"]`) + err = txRepo.UpdateBackupCodes(ctx, userID, `["h2"]`) require.NoError(t, err) err = tx.Commit() require.NoError(t, err) - record, err := repo.GetByUserID(ctx, "user-with-tx-1") + record, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.Equal(t, `["h2"]`, record.BackupCodes) }, @@ -112,20 +106,21 @@ func TestWithTx(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-with-tx-2") - createTestTOTPRecord(t, ctx, repo, "user-with-tx-2") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) tx, err := db.BeginTx(ctx, nil) require.NoError(t, err) txRepo := repo.WithTx(tx) - err = txRepo.UpdateBackupCodes(ctx, "user-with-tx-2", `["rolled-back"]`) + err = txRepo.UpdateBackupCodes(ctx, userID, `["rolled-back"]`) require.NoError(t, err) err = tx.Rollback() require.NoError(t, err) - record, err := repo.GetByUserID(ctx, "user-with-tx-2") + record, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.Equal(t, `["h1","h2"]`, record.BackupCodes) }, @@ -144,14 +139,15 @@ func TestGetByUserID(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-get-1") - created := createTestTOTPRecord(t, ctx, repo, "user-get-1") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + created := createTestTOTPRecord(t, ctx, repo, userID) - record, err := repo.GetByUserID(ctx, "user-get-1") + record, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.NotNil(t, record) require.Equal(t, created.ID, record.ID) - require.Equal(t, "user-get-1", record.UserID) + require.Equal(t, userID, record.UserID) require.Equal(t, "encrypted-secret", record.Secret) require.Equal(t, `["h1","h2"]`, record.BackupCodes) }, @@ -163,7 +159,7 @@ func TestGetByUserID(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - record, err := repo.GetByUserID(ctx, "missing-user") + record, err := repo.GetByUserID(ctx, "00000000-0000-0000-0000-000000000000") require.NoError(t, err) require.Nil(t, record) }, @@ -182,13 +178,14 @@ func TestCreate(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-create-1") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) - record, err := repo.Create(ctx, "user-create-1", "secret-value", `["a","b"]`) + record, err := repo.Create(ctx, userID, "secret-value", `["a","b"]`) require.NoError(t, err) require.NotNil(t, record) require.NotEmpty(t, record.ID) - require.Equal(t, "user-create-1", record.UserID) + require.Equal(t, userID, record.UserID) require.Equal(t, "secret-value", record.Secret) require.Equal(t, `["a","b"]`, record.BackupCodes) require.False(t, record.CreatedAt.IsZero()) @@ -202,12 +199,13 @@ func TestCreate(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-create-2") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) - _, err := repo.Create(ctx, "user-create-2", "secret-value", `["a","b"]`) + _, err := repo.Create(ctx, userID, "secret-value", `["a","b"]`) require.NoError(t, err) - record, err := repo.GetByUserID(ctx, "user-create-2") + record, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.NotNil(t, record) require.Equal(t, "secret-value", record.Secret) @@ -228,13 +226,14 @@ func TestUpdateBackupCodes(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-update-1") - createTestTOTPRecord(t, ctx, repo, "user-update-1") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) - err := repo.UpdateBackupCodes(ctx, "user-update-1", `["h2"]`) + err := repo.UpdateBackupCodes(ctx, userID, `["h2"]`) require.NoError(t, err) - record, err := repo.GetByUserID(ctx, "user-update-1") + record, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.Equal(t, `["h2"]`, record.BackupCodes) }, @@ -246,17 +245,18 @@ func TestUpdateBackupCodes(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-update-2") - createTestTOTPRecord(t, ctx, repo, "user-update-2") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) - before, err := repo.GetByUserID(ctx, "user-update-2") + before, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) time.Sleep(5 * time.Millisecond) - err = repo.UpdateBackupCodes(ctx, "user-update-2", `["changed"]`) + err = repo.UpdateBackupCodes(ctx, userID, `["changed"]`) require.NoError(t, err) - after, err := repo.GetByUserID(ctx, "user-update-2") + after, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.True(t, after.UpdatedAt.After(before.UpdatedAt) || after.UpdatedAt.Equal(before.UpdatedAt)) }, @@ -268,13 +268,14 @@ func TestUpdateBackupCodes(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-update-3") - createTestTOTPRecord(t, ctx, repo, "user-update-3") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) - err := repo.UpdateBackupCodes(ctx, "user-update-3", `["only-one"]`) + err := repo.UpdateBackupCodes(ctx, userID, `["only-one"]`) require.NoError(t, err) - record, err := repo.GetByUserID(ctx, "user-update-3") + record, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.Equal(t, `["only-one"]`, record.BackupCodes) require.NotEqual(t, `["h1","h2"]`, record.BackupCodes) @@ -294,14 +295,15 @@ func TestCompareAndSwapBackupCodes(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-cas-1") - createTestTOTPRecord(t, ctx, repo, "user-cas-1") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) - updated, err := repo.CompareAndSwapBackupCodes(ctx, "user-cas-1", `["h1","h2"]`, `["h2"]`) + updated, err := repo.CompareAndSwapBackupCodes(ctx, userID, `["h1","h2"]`, `["h2"]`) require.NoError(t, err) require.True(t, updated) - record, err := repo.GetByUserID(ctx, "user-cas-1") + record, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.Equal(t, `["h2"]`, record.BackupCodes) }, @@ -313,14 +315,15 @@ func TestCompareAndSwapBackupCodes(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-cas-2") - createTestTOTPRecord(t, ctx, repo, "user-cas-2") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) - updated, err := repo.CompareAndSwapBackupCodes(ctx, "user-cas-2", `["bad"]`, `[]`) + updated, err := repo.CompareAndSwapBackupCodes(ctx, userID, `["bad"]`, `[]`) require.NoError(t, err) require.False(t, updated) - record, err := repo.GetByUserID(ctx, "user-cas-2") + record, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.Equal(t, `["h1","h2"]`, record.BackupCodes) }, @@ -339,13 +342,14 @@ func TestDeleteByUserID(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-delete-1") - createTestTOTPRecord(t, ctx, repo, "user-delete-1") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) - err := repo.DeleteByUserID(ctx, "user-delete-1") + err := repo.DeleteByUserID(ctx, userID) require.NoError(t, err) - record, err := repo.GetByUserID(ctx, "user-delete-1") + record, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.Nil(t, record) }, @@ -357,7 +361,7 @@ func TestDeleteByUserID(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - err := repo.DeleteByUserID(ctx, "missing-user") + err := repo.DeleteByUserID(ctx, "00000000-0000-0000-0000-000000000000") require.NoError(t, err) }, }, @@ -375,12 +379,13 @@ func TestIsEnabled(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-enabled-1") - createTestTOTPRecord(t, ctx, repo, "user-enabled-1") - err := repo.SetEnabled(ctx, "user-enabled-1", true) + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) + err := repo.SetEnabled(ctx, userID, true) require.NoError(t, err) - enabled, err := repo.IsEnabled(ctx, "user-enabled-1") + enabled, err := repo.IsEnabled(ctx, userID) require.NoError(t, err) require.True(t, enabled) }, @@ -392,10 +397,11 @@ func TestIsEnabled(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-enabled-2") - createTestTOTPRecord(t, ctx, repo, "user-enabled-2") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) - enabled, err := repo.IsEnabled(ctx, "user-enabled-2") + enabled, err := repo.IsEnabled(ctx, userID) require.NoError(t, err) require.False(t, enabled) }, @@ -407,7 +413,7 @@ func TestIsEnabled(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - enabled, err := repo.IsEnabled(ctx, "missing-user") + enabled, err := repo.IsEnabled(ctx, "00000000-0000-0000-0000-000000000000") require.NoError(t, err) require.False(t, enabled) }, @@ -426,13 +432,14 @@ func TestSetEnabled(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-set-enabled-1") - createTestTOTPRecord(t, ctx, repo, "user-set-enabled-1") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) - err := repo.SetEnabled(ctx, "user-set-enabled-1", true) + err := repo.SetEnabled(ctx, userID, true) require.NoError(t, err) - record, err := repo.GetByUserID(ctx, "user-set-enabled-1") + record, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.True(t, record.Enabled) }, @@ -444,17 +451,18 @@ func TestSetEnabled(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-set-enabled-2") - createTestTOTPRecord(t, ctx, repo, "user-set-enabled-2") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) - before, err := repo.GetByUserID(ctx, "user-set-enabled-2") + before, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) time.Sleep(5 * time.Millisecond) - err = repo.SetEnabled(ctx, "user-set-enabled-2", true) + err = repo.SetEnabled(ctx, userID, true) require.NoError(t, err) - after, err := repo.GetByUserID(ctx, "user-set-enabled-2") + after, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.True(t, after.UpdatedAt.After(before.UpdatedAt) || after.UpdatedAt.Equal(before.UpdatedAt)) }, @@ -466,15 +474,16 @@ func TestSetEnabled(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-set-enabled-3") - createTestTOTPRecord(t, ctx, repo, "user-set-enabled-3") - err := repo.SetEnabled(ctx, "user-set-enabled-3", true) + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTOTPRecord(t, ctx, repo, userID) + err := repo.SetEnabled(ctx, userID, true) require.NoError(t, err) - err = repo.SetEnabled(ctx, "user-set-enabled-3", false) + err = repo.SetEnabled(ctx, userID, false) require.NoError(t, err) - record, err := repo.GetByUserID(ctx, "user-set-enabled-3") + record, err := repo.GetByUserID(ctx, userID) require.NoError(t, err) require.False(t, record.Enabled) }, @@ -493,14 +502,15 @@ func TestGetTrustedDeviceByToken(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-device-get-1") - created := createTestTrustedDevice(t, ctx, repo, "user-device-get-1", "token-1", "ua-1") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + created := createTestTrustedDevice(t, ctx, repo, userID, "token-1", "ua-1") device, err := repo.GetTrustedDeviceByToken(ctx, "token-1") require.NoError(t, err) require.NotNil(t, device) require.Equal(t, created.ID, device.ID) - require.Equal(t, "user-device-get-1", device.UserID) + require.Equal(t, userID, device.UserID) require.Equal(t, "token-1", device.Token) require.Equal(t, "ua-1", device.UserAgent) }, @@ -531,14 +541,15 @@ func TestCreateTrustedDevice(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-device-create-1") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) expiresAt := time.Now().UTC().Add(48 * time.Hour) - device, err := repo.CreateTrustedDevice(ctx, "user-device-create-1", "token-create-1", "ua-create", expiresAt) + device, err := repo.CreateTrustedDevice(ctx, userID, "token-create-1", "ua-create", expiresAt) require.NoError(t, err) require.NotNil(t, device) require.NotEmpty(t, device.ID) - require.Equal(t, "user-device-create-1", device.UserID) + require.Equal(t, userID, device.UserID) require.Equal(t, "token-create-1", device.Token) require.Equal(t, "ua-create", device.UserAgent) require.WithinDuration(t, expiresAt, device.ExpiresAt, time.Second) @@ -551,16 +562,17 @@ func TestCreateTrustedDevice(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-device-create-2") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) expiresAt := time.Now().UTC().Add(24 * time.Hour) - _, err := repo.CreateTrustedDevice(ctx, "user-device-create-2", "token-create-2", "ua-create", expiresAt) + _, err := repo.CreateTrustedDevice(ctx, userID, "token-create-2", "ua-create", expiresAt) require.NoError(t, err) device, err := repo.GetTrustedDeviceByToken(ctx, "token-create-2") require.NoError(t, err) require.NotNil(t, device) - require.Equal(t, "user-device-create-2", device.UserID) + require.Equal(t, userID, device.UserID) }, }, } @@ -577,8 +589,9 @@ func TestRefreshTrustedDevice(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-refresh-1") - createTestTrustedDevice(t, ctx, repo, "user-refresh-1", "token-refresh-1", "ua") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTrustedDevice(t, ctx, repo, userID, "token-refresh-1", "ua") newExpiry := time.Now().UTC().Add(72 * time.Hour) err := repo.RefreshTrustedDevice(ctx, "token-refresh-1", newExpiry) @@ -597,9 +610,10 @@ func TestRefreshTrustedDevice(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-refresh-2") - createTestTrustedDevice(t, ctx, repo, "user-refresh-2", "token-refresh-2a", "ua-a") - original := createTestTrustedDevice(t, ctx, repo, "user-refresh-2", "token-refresh-2b", "ua-b") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTrustedDevice(t, ctx, repo, userID, "token-refresh-2a", "ua-a") + original := createTestTrustedDevice(t, ctx, repo, userID, "token-refresh-2b", "ua-b") newExpiry := time.Now().UTC().Add(96 * time.Hour) err := repo.RefreshTrustedDevice(ctx, "token-refresh-2a", newExpiry) @@ -625,11 +639,12 @@ func TestDeleteTrustedDevicesByUserID(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-device-delete-1") - createTestTrustedDevice(t, ctx, repo, "user-device-delete-1", "token-del-1", "ua") - createTestTrustedDevice(t, ctx, repo, "user-device-delete-1", "token-del-2", "ua") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) + createTestTrustedDevice(t, ctx, repo, userID, "token-del-1", "ua") + createTestTrustedDevice(t, ctx, repo, userID, "token-del-2", "ua") - err := repo.DeleteTrustedDevicesByUserID(ctx, "user-device-delete-1") + err := repo.DeleteTrustedDevicesByUserID(ctx, userID) require.NoError(t, err) dev1, err := repo.GetTrustedDeviceByToken(ctx, "token-del-1") @@ -648,12 +663,14 @@ func TestDeleteTrustedDevicesByUserID(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-device-delete-2a") - createTestUser(t, ctx, db, "user-device-delete-2b") - createTestTrustedDevice(t, ctx, repo, "user-device-delete-2a", "token-del-3", "ua") - createTestTrustedDevice(t, ctx, repo, "user-device-delete-2b", "token-del-4", "ua") + userID1 := uuid.New().String() + userID2 := uuid.New().String() + createTestUser(t, ctx, db, userID1) + createTestUser(t, ctx, db, userID2) + createTestTrustedDevice(t, ctx, repo, userID1, "token-del-3", "ua") + createTestTrustedDevice(t, ctx, repo, userID2, "token-del-4", "ua") - err := repo.DeleteTrustedDevicesByUserID(ctx, "user-device-delete-2a") + err := repo.DeleteTrustedDevicesByUserID(ctx, userID1) require.NoError(t, err) removed, err := repo.GetTrustedDeviceByToken(ctx, "token-del-3") @@ -672,7 +689,7 @@ func TestDeleteTrustedDevicesByUserID(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - err := repo.DeleteTrustedDevicesByUserID(ctx, "missing-user") + err := repo.DeleteTrustedDevicesByUserID(ctx, "00000000-0000-0000-0000-000000000000") require.NoError(t, err) }, }, @@ -690,11 +707,12 @@ func TestDeleteExpiredTrustedDevices(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-expired-1") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) - _, err := repo.CreateTrustedDevice(ctx, "user-expired-1", "token-expired", "ua", time.Now().UTC().Add(-2*time.Hour)) + _, err := repo.CreateTrustedDevice(ctx, userID, "token-expired", "ua", time.Now().UTC().Add(-2*time.Hour)) require.NoError(t, err) - _, err = repo.CreateTrustedDevice(ctx, "user-expired-1", "token-valid", "ua", time.Now().UTC().Add(2*time.Hour)) + _, err = repo.CreateTrustedDevice(ctx, userID, "token-valid", "ua", time.Now().UTC().Add(2*time.Hour)) require.NoError(t, err) err = repo.DeleteExpiredTrustedDevices(ctx) @@ -712,9 +730,10 @@ func TestDeleteExpiredTrustedDevices(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-expired-2") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) - _, err := repo.CreateTrustedDevice(ctx, "user-expired-2", "token-valid-2", "ua", time.Now().UTC().Add(3*time.Hour)) + _, err := repo.CreateTrustedDevice(ctx, userID, "token-valid-2", "ua", time.Now().UTC().Add(3*time.Hour)) require.NoError(t, err) err = repo.DeleteExpiredTrustedDevices(ctx) @@ -732,9 +751,10 @@ func TestDeleteExpiredTrustedDevices(t *testing.T) { repo := repository.NewTOTPRepository(db) ctx := context.Background() - createTestUser(t, ctx, db, "user-expired-3") + userID := uuid.New().String() + createTestUser(t, ctx, db, userID) - _, err := repo.CreateTrustedDevice(ctx, "user-expired-3", "token-boundary", "ua", time.Now().UTC().Add(1*time.Second)) + _, err := repo.CreateTrustedDevice(ctx, userID, "token-boundary", "ua", time.Now().UTC().Add(1*time.Second)) require.NoError(t, err) err = repo.DeleteExpiredTrustedDevices(ctx)