package postgres import ( "context" "errors" "os" "sync" "testing" "time" "drive.local/drivev2/internal/adapters/authn" "drive.local/drivev2/internal/application" ) type integrationPasswordHasher struct{} func (integrationPasswordHasher) Hash(password string) (string, error) { return "integration:" + password, nil } func (integrationPasswordHasher) Verify(password, encoded string) (bool, error) { return encoded == "integration:"+password, nil } func TestMigrationsAndStore(t *testing.T) { databaseURL := os.Getenv("DRIVE_TEST_DATABASE_URL") if databaseURL == "" { t.Skip("DRIVE_TEST_DATABASE_URL is not set") } if err := Migrate(databaseURL); err != nil { t.Fatalf("first migration run failed: %v", err) } if err := Migrate(databaseURL); err != nil { t.Fatalf("second migration run failed: %v", err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() store, err := Open(ctx, databaseURL) if err != nil { t.Fatalf("Open returned an error: %v", err) } defer store.Close() if err := store.DatabaseHealth(ctx); err != nil { t.Fatalf("DatabaseHealth returned an error: %v", err) } initialized, err := store.OwnerExists(ctx) if err != nil { t.Fatalf("OwnerExists returned an error: %v", err) } if initialized { t.Fatal("OwnerExists = true on a fresh database, want false") } authentication := application.NewAuthenticationService( store, integrationPasswordHasher{}, authn.CredentialGenerator{}, authn.SystemClock{}, ) inputs := []application.SetupOwnerInput{ {Username: "owner-one", DisplayName: "Owner One", Password: "correct horse battery staple"}, {Username: "owner-two", DisplayName: "Owner Two", Password: "correct horse battery staple"}, } results := make([]application.SetupOwnerResult, len(inputs)) errorsByAttempt := make([]error, len(inputs)) var waitGroup sync.WaitGroup for index := range inputs { waitGroup.Add(1) go func() { defer waitGroup.Done() results[index], errorsByAttempt[index] = authentication.SetupOwner(ctx, inputs[index]) }() } waitGroup.Wait() winner := -1 losers := 0 for index, setupErr := range errorsByAttempt { switch { case setupErr == nil: winner = index case errors.Is(setupErr, application.ErrSetupAlreadyComplete): losers++ default: t.Fatalf("setup attempt %d returned unexpected error: %v", index, setupErr) } } if winner < 0 || losers != 1 { t.Fatalf("winner = %d, losing attempts = %d, want one of each", winner, losers) } if len(results[winner].RecoveryCodes) != application.RecoveryCodeCount { t.Fatalf("recovery code count = %d, want %d", len(results[winner].RecoveryCodes), application.RecoveryCodeCount) } counts, err := store.queries.GetAuthenticationInvariantCounts(ctx) if err != nil { t.Fatalf("GetAuthenticationInvariantCounts returned an error: %v", err) } if counts.Owners != 1 || counts.Roots != 1 || counts.RecoveryCodes != 10 || counts.Changes != 1 || counts.AuditEvents != 1 || counts.Sessions != 1 || counts.ActiveSessions != 1 { t.Fatalf("unexpected post-setup counts: %#v", counts) } loggedIn, err := authentication.Login(ctx, application.LoginInput{ Username: inputs[winner].Username, Password: inputs[winner].Password, }) if err != nil { t.Fatalf("Login returned an error: %v", err) } current, err := authentication.CurrentSession(ctx, loggedIn.SessionToken) if err != nil { t.Fatalf("CurrentSession returned an error: %v", err) } if current.ID != loggedIn.Session.ID { t.Fatalf("current session ID = %q, want %q", current.ID, loggedIn.Session.ID) } if err := authentication.Logout(ctx, loggedIn.SessionToken, loggedIn.CSRFToken, "wrong-csrf"); !errors.Is(err, application.ErrCSRFValidation) { t.Fatalf("Logout with invalid CSRF error = %v, want %v", err, application.ErrCSRFValidation) } if err := authentication.Logout(ctx, loggedIn.SessionToken, loggedIn.CSRFToken, loggedIn.CSRFToken); err != nil { t.Fatalf("Logout returned an error: %v", err) } if _, err := authentication.CurrentSession(ctx, loggedIn.SessionToken); !errors.Is(err, application.ErrSessionNotFound) { t.Fatalf("CurrentSession after logout error = %v, want %v", err, application.ErrSessionNotFound) } counts, err = store.queries.GetAuthenticationInvariantCounts(ctx) if err != nil { t.Fatalf("GetAuthenticationInvariantCounts after logout returned an error: %v", err) } if counts.Sessions != 2 || counts.ActiveSessions != 1 || counts.AuditEvents != 3 { t.Fatalf("unexpected post-logout counts: %#v", counts) } }