Files
member-console/cmd/bootstate_db_test.go
cgalo5758 d1ac278e6b Open every test database through dbtest
internal/testkit/dbtest opens TEST_DATABASE_URL and E2E_DATABASE_URL, never
migrates, and fails with "the test database is behind; run
test/reset-test-db.sh" when the core ledger lacks the highest version of
db.BaseSources(). The opener helpers except the scratch-database and
container openers (26 of the 29) and the inline openers of the database
tests call it; helpers that did more than open (providers, fake farms, the
FedWiki provider row, cleanup of override rows) keep that setup. The
stripe/workflows and workflows/entitlements openers keep their search_path
through WithSearchPath. The closed-database seam tests use Closed.

TestMigration18SetsExistingFedwikiRulesToForceReduce runs on a scratch
database through Scratch and UpTo instead of rolling the shared core ledger
back through goose's global state, because no opener migrates the shared
database any more.

The in-package forms tests of internal/server reached the integrations'
forms only because an external test file imported internal/migrate; a test
file now links internal/integrations explicitly.

TestRegisterProviders_DB is re-pinned from gocognit 37 to 34.

New tests: TestLedgerCheckRefusesAnEmptyDatabaseAndAcceptsTheTestDatabase
(dbtest), TestOpenRefusesAnEmptyDatabaseAndAcceptsTheTestDatabase (dbtest,
through opened, the path Open and OpenE2E share),
TestKitPackagesReachOnlyTheirAllowedModulePackages (testkit). The skip
messages of the openers are unified to "TEST_DATABASE_URL not set, skipping
integration test". 4 breaks: Open skipping its ledger check and the ledger
check accepting an empty database fail the new tests; a kit row that no
longer allows internal/testkit fails the import guard; leaving the fedwiki
rules as they were in migration 00018 fails the migration 18 test on both
sides.
2026-10-07 16:22:18 -05:00

109 lines
4.0 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
package cmd
import (
"context"
"fmt"
"strings"
"testing"
"time"
"git.coopcloud.tech/wiki-cafe/member-console/internal/db"
"git.coopcloud.tech/wiki-cafe/member-console/internal/integration"
"git.coopcloud.tech/wiki-cafe/member-console/internal/integrations"
"git.coopcloud.tech/wiki-cafe/member-console/internal/testkit/dbtest"
)
// bindTestIntegration is a made-up integration owning one key, which caches
// the connection identifier it is handed.
type bindTestIntegration struct {
manifest integration.Manifest
bound *string
}
func (b bindTestIntegration) Key() string { return b.manifest.Key }
func (b bindTestIntegration) MigrationSource() db.MigrationSource { return db.MigrationSource{} }
func (b bindTestIntegration) Provider() integration.ProviderSource {
return manifestOf(b.manifest)
}
func (b bindTestIntegration) BindConnection(id string) { *b.bound = id }
type manifestOf integration.Manifest
func (m manifestOf) ProviderManifest() integration.Manifest { return integration.Manifest(m) }
// TestBindIntegrationConnections covers task 4.5's wiring: after
// registration, a key-owning integration that caches its connection gets the
// identifier registration ensured, and boot fails naming the provider when
// that provider's connection row is missing.
func TestBindIntegrationConnections(t *testing.T) {
database := dbtest.Open(t)
ctx := context.Background()
provider := fmt.Sprintf("bindt%d", time.Now().UnixNano())
key := provider + "_seats"
// The connection's identifier, kept for the cleanup: the test deletes the
// connection row itself, and the outbox row its insert wrote must not
// outlive the test as a pending row naming no connection.
var connectionID string
t.Cleanup(func() {
if _, err := database.ExecContext(ctx,
`DELETE FROM core.desired_state_outbox WHERE subject_kind = 'connection' AND subject_id::text = $1`,
connectionID); err != nil {
t.Errorf("cleanup: %v", err)
}
for _, q := range []string{
`DELETE FROM core.provider_connections WHERE provider = $1`,
`DELETE FROM core.resource_keys WHERE provider = $1 OR resource_key = $1 || '_seats'`,
`DELETE FROM core.providers WHERE provider = $1`,
} {
if _, err := database.ExecContext(ctx, q, provider); err != nil {
t.Errorf("cleanup: %v", err)
}
}
})
if _, err := database.ExecContext(ctx,
`INSERT INTO core.resource_keys (resource_key, display_name, unit, kind) VALUES ($1, 'Bind Key', 'unit', 'boolean')`,
key); err != nil {
t.Fatal(err)
}
var bound string
integ := bindTestIntegration{
manifest: integration.Manifest{
Key: provider,
Kind: integration.KindProvisioning,
DisplayName: "Bind Test",
ResourceKeys: []string{key},
Recipients: map[string]integration.RecipientKind{key: integration.RecipientPerson},
},
bound: &bound,
}
if err := integration.RegisterProviders(ctx, database, []integration.ProviderSource{integ.Provider()}); err != nil {
t.Fatalf("RegisterProviders: %v", err)
}
if err := database.QueryRowContext(ctx,
`SELECT connection_id FROM core.provider_connections WHERE provider = $1`, provider).Scan(&connectionID); err != nil {
t.Fatal(err)
}
if err := bindIntegrationConnections(ctx, database, []integrations.Integration{integ}); err != nil {
t.Fatalf("bindIntegrationConnections: %v", err)
}
if bound != connectionID {
t.Errorf("the integration cached %q, want its connection %q", bound, connectionID)
}
// The connection row removed after registration: boot fails, naming the
// provider.
if _, err := database.ExecContext(ctx, `DELETE FROM core.provider_connections WHERE provider = $1`, provider); err != nil {
t.Fatal(err)
}
err := bindIntegrationConnections(ctx, database, []integrations.Integration{integ})
if err == nil || !strings.Contains(err.Error(), provider) {
t.Fatalf("error = %v, want one naming provider %s", err, provider)
}
}