Files
configcenter/internal/store/postgres/postgres_test.go

123 lines
4.1 KiB
Go

package postgres
import (
"context"
"errors"
"os"
"strconv"
"testing"
"time"
"github.com/longpeng/configcenter/internal/domain"
storepkg "github.com/longpeng/configcenter/internal/store"
)
func TestMigrationChecksum(t *testing.T) {
const want = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
if got := migrationChecksum([]byte("abc")); got != want {
t.Fatalf("unexpected SHA-256 checksum: got %s, want %s", got, want)
}
}
func TestUserLifecycleAndAuditIntegration(t *testing.T) {
databaseURL := os.Getenv("TEST_DATABASE_URL")
if databaseURL == "" {
t.Skip("TEST_DATABASE_URL is not configured")
}
ctx := context.Background()
repository, err := Open(ctx, databaseURL)
if err != nil {
t.Fatal(err)
}
defer repository.Close()
if err := repository.Migrate(ctx); err != nil {
t.Fatal(err)
}
suffix := strconv.FormatInt(time.Now().UnixNano(), 36)
rootName := "root-" + suffix
backupName := "backup-" + suffix
appCode := "audit-" + suffix
defer func() {
_, _ = repository.pool.Exec(context.Background(), `DELETE FROM users WHERE username IN ($1,$2)`, rootName, backupName)
_, _ = repository.pool.Exec(context.Background(), `DELETE FROM applications WHERE app_code=$1`, appCode)
}()
if err := repository.EnsureBootstrapAdmin(ctx, rootName, "root-hash", "Root"); err != nil {
t.Fatal(err)
}
root, err := repository.FindUserByUsername(ctx, rootName)
if err != nil {
t.Fatal(err)
}
if root.TokenVersion != 1 || root.UpdatedAt.IsZero() {
t.Fatalf("unexpected bootstrap credential: %#v", root)
}
backup, err := repository.CreateUser(ctx, domain.User{Username: backupName, DisplayName: "Backup", IsAdmin: true, UpdatedBy: rootName}, "backup-hash")
if err != nil {
t.Fatal(err)
}
if _, err := repository.SetUserDisabled(ctx, root.ID, true, backupName); err != nil {
t.Fatal(err)
}
rootAfterDisable, err := repository.FindUserByID(ctx, root.ID)
if err != nil {
t.Fatal(err)
}
if !rootAfterDisable.Disabled || rootAfterDisable.TokenVersion != 2 {
t.Fatalf("disable did not rotate token version: %#v", rootAfterDisable)
}
if err := repository.DeleteUser(ctx, backup.ID, rootName); !errors.Is(err, storepkg.ErrLastAdmin) {
t.Fatalf("deleting last active admin must fail: %v", err)
}
if _, err := repository.SetUserDisabled(ctx, root.ID, false, backupName); err != nil {
t.Fatal(err)
}
if err := repository.ResetUserPassword(ctx, backup.ID, "new-backup-hash", rootName); err != nil {
t.Fatal(err)
}
backupCredential, err := repository.FindUserByID(ctx, backup.ID)
if err != nil {
t.Fatal(err)
}
if backupCredential.TokenVersion != 2 || backupCredential.PasswordHash != "new-backup-hash" {
t.Fatalf("password reset did not rotate credential: %#v", backupCredential)
}
app, err := repository.CreateApplication(ctx, domain.Application{Code: appCode, Name: "Audit Test", UpdatedBy: rootName})
if err != nil {
t.Fatal(err)
}
if err := repository.SetUserAppRole(ctx, domain.UserAppRole{UserID: backup.ID, AppID: app.ID, Role: "viewer", UpdatedBy: rootName}); err != nil {
t.Fatal(err)
}
backupCredential, _ = repository.FindUserByID(ctx, backup.ID)
if backupCredential.TokenVersion != 3 {
t.Fatalf("role change did not rotate token version: %d", backupCredential.TokenVersion)
}
if err := repository.DeleteUserAppRole(ctx, backup.ID, app.ID, rootName); err != nil {
t.Fatal(err)
}
backupCredential, _ = repository.FindUserByID(ctx, backup.ID)
if backupCredential.TokenVersion != 4 {
t.Fatalf("role removal did not rotate token version: %d", backupCredential.TokenVersion)
}
page, err := repository.ListAuditLogs(ctx, domain.AuditLogQuery{Actor: rootName, TargetType: "user", Limit: 1})
if err != nil {
t.Fatal(err)
}
if len(page.Items) != 1 || page.NextCursor == nil {
t.Fatalf("expected filtered cursor page: %#v", page)
}
next, err := repository.ListAuditLogs(ctx, domain.AuditLogQuery{Actor: rootName, TargetType: "user", Limit: 10, BeforeID: *page.NextCursor})
if err != nil {
t.Fatal(err)
}
if len(next.Items) == 0 || next.Items[0].ID >= page.Items[0].ID {
t.Fatalf("audit cursor did not advance: first=%#v next=%#v", page.Items, next.Items)
}
}