133 lines
4.2 KiB
Go
133 lines
4.2 KiB
Go
/*
|
|
Copyright 2026.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package registry
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
func TestRegistryLifecycle(t *testing.T) {
|
|
dsn := os.Getenv("POSTGRES_TEST_DSN")
|
|
if dsn == "" {
|
|
t.Skip("POSTGRES_TEST_DSN is not set")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
pool, err := pgxpool.New(ctx, dsn)
|
|
if err != nil {
|
|
t.Fatalf("create PostgreSQL pool: %v", err)
|
|
}
|
|
t.Cleanup(pool.Close)
|
|
if err := pool.Ping(ctx); err != nil {
|
|
t.Fatalf("ping PostgreSQL: %v", err)
|
|
}
|
|
|
|
dropRegistrySchema(t, ctx, pool)
|
|
t.Cleanup(func() { dropRegistrySchema(t, ctx, pool) })
|
|
|
|
store := NewStore(pool)
|
|
if err := store.Bootstrap(ctx); err != nil {
|
|
t.Fatalf("Bootstrap() error = %v", err)
|
|
}
|
|
if err := store.Bootstrap(ctx); err != nil {
|
|
t.Fatalf("second Bootstrap() error = %v", err)
|
|
}
|
|
|
|
owner := testOwnership("tenant-uid-1", "netbox", "netbox", "netbox", "postgresql-tenants/netbox/netbox")
|
|
result, err := store.Claim(ctx, owner)
|
|
if err != nil || result != ClaimCreated {
|
|
t.Fatalf("first Claim() = %q, %v; want %q, nil", result, err, ClaimCreated)
|
|
}
|
|
result, err = store.Claim(ctx, owner)
|
|
if err != nil || result != ClaimOwned {
|
|
t.Fatalf("second Claim() = %q, %v; want %q, nil", result, err, ClaimOwned)
|
|
}
|
|
|
|
record, err := store.Get(ctx, owner.InstanceUID, owner.TenantUID)
|
|
if err != nil {
|
|
t.Fatalf("Get() error = %v", err)
|
|
}
|
|
if record.Ownership != owner || !record.Managed || record.RetainedAt != nil {
|
|
t.Fatalf("Get() = %#v; want matching managed owner", record)
|
|
}
|
|
|
|
conflict := testOwnership("tenant-uid-2", "other", owner.DatabaseName, "other", "postgresql-tenants/other/other")
|
|
if _, err := store.Claim(ctx, conflict); !errors.Is(err, ErrConflict) {
|
|
t.Fatalf("conflicting Claim() error = %v, want ErrConflict", err)
|
|
}
|
|
|
|
if err := store.MarkRetained(ctx, owner); err != nil {
|
|
t.Fatalf("MarkRetained() error = %v", err)
|
|
}
|
|
if err := store.MarkRetained(ctx, owner); err != nil {
|
|
t.Fatalf("second MarkRetained() error = %v", err)
|
|
}
|
|
record, err = store.Get(ctx, owner.InstanceUID, owner.TenantUID)
|
|
if err != nil || record.Managed || record.RetainedAt == nil {
|
|
t.Fatalf("retained Get() = %#v, %v; want unmanaged tombstone", record, err)
|
|
}
|
|
if err := store.Delete(ctx, owner); !errors.Is(err, ErrConflict) {
|
|
t.Fatalf("Delete(retained) error = %v, want ErrConflict", err)
|
|
}
|
|
if _, err := store.Claim(ctx, owner); !errors.Is(err, ErrConflict) {
|
|
t.Fatalf("Claim(retained) error = %v, want ErrConflict", err)
|
|
}
|
|
|
|
deletable := testOwnership("tenant-uid-3", "gitea", "gitea", "gitea", "postgresql-tenants/gitea/gitea")
|
|
if _, err := store.Claim(ctx, deletable); err != nil {
|
|
t.Fatalf("Claim(deletable) error = %v", err)
|
|
}
|
|
if err := store.Delete(ctx, deletable); err != nil {
|
|
t.Fatalf("Delete() error = %v", err)
|
|
}
|
|
if err := store.Delete(ctx, deletable); err != nil {
|
|
t.Fatalf("second Delete() error = %v", err)
|
|
}
|
|
if _, err := store.Get(ctx, deletable.InstanceUID, deletable.TenantUID); !errors.Is(err, ErrNotFound) {
|
|
t.Fatalf("Get(deleted) error = %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func testOwnership(tenantUID, tenantName, databaseName, roleName, credentialPath string) Ownership {
|
|
return Ownership{
|
|
InstanceUID: "instance-uid-1",
|
|
TenantUID: tenantUID,
|
|
TenantNamespace: tenantName,
|
|
TenantName: tenantName,
|
|
DatabaseName: databaseName,
|
|
RoleName: roleName,
|
|
CredentialPath: credentialPath,
|
|
}
|
|
}
|
|
|
|
type schemaDropper interface {
|
|
Exec(context.Context, string, ...any) (pgconn.CommandTag, error)
|
|
}
|
|
|
|
func dropRegistrySchema(t *testing.T, ctx context.Context, db schemaDropper) {
|
|
t.Helper()
|
|
if _, err := db.Exec(ctx, `DROP SCHEMA IF EXISTS postgresql_tenant_operator CASCADE`); err != nil {
|
|
t.Fatalf("drop registry schema: %v", err)
|
|
}
|
|
}
|