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.
109 lines
4.0 KiB
Go
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)
|
|
}
|
|
}
|