Files
member-console/test/mockshot-nuke.sql
T
cgalo5758 f9c9b3225e Run the console on a runtime user and migrate on a second connection string
Fourth section of #23. db-dsn now names the console's runtime user, and
the new db-migrate-dsn (or db-migrate-dsn-file) names the user that runs
migrations. start opens both, migrates on the migration connection,
grants console_runtime to the runtime user there (WITH INHERIT TRUE, the
name quoted by the database), closes it, and serves on the runtime pool
alone. With db-migrate-dsn left out, start runs no migration and refuses
when the schema is behind its code, which is how a host runs migrations
as a separate step; migrate up, down and status use the migration string
and refuse without it, with no fallback to db-dsn.

Both pools are opened from one pgx.ParseConfig: the migration pool's
search_path is public and both pools' role is none, set in the startup
parameters after every key of either name is removed case-insensitively,
so neither a setting the database or role carries nor a key the string
repeats in another case moves a session off its login or its path.
PendingMigrations reads each stream's ledger by qualified name inside a
transaction pinned to search_path pg_catalog, pg_temp.

The test stack runs on two users: postgres-seed creates
member_console_runtime in new and existing volumes, bootstrap-stack.sh
writes MC_DB_DSN as that user and MC_DB_MIGRATE_DSN as member_console,
and the four scripts refuse a .env without the migration string, with
the command that regenerates it. Scripts and fixtures that need a
superuser on the application database (the reset, the screens'
timestamp pinning) use the migration string.

This worktree's stack was regenerated with that command and the console
booted as member_console_runtime: its sessions were the runtime user's
only, no migration-user session stayed open, and the runtime user could
not insert into core.pool_provisions.

Full suite on reset databases: 4277 pass, 15 skip, none failed. The
walkthroughs' names, results and skip messages match the previous
section's run. make screens lists three changed screens, all data:
regenerated ids on operator-person-detail and
operator-billing-invoice-detail (the demo snapshot was rebuilt) and a
domain's age on operator-domains (294d to 295d); the baseline was not
accepted. make lint passes.

Deliberate breaks, scripts/mutation-check -side new, all CAUGHT (36):
  dbRuntime runs the migrations on the runtime pool
  dbRuntime skips the boot grant
  dbRuntime leaves the migration pool open
  dbRuntime skips the pending check when db-migrate-dsn is unset
  dbRuntime migrates on db-dsn when db-migrate-dsn is unset
  GrantRuntimeRole without WITH INHERIT TRUE
  GrantRuntimeRole with %s in place of %I
  PendingMigrations takes a missing ledger as current
  PendingMigrations resolves the ledger without the public. qualifier
  the migration pool opened without search_path=public
  the migration pool opened without role=none
  the runtime pool opened without role=none
  ConnectMigration keeps the search_path and role the string carries
  dbMigration falls back to db-dsn
  ValidateStart accepts a malformed db-migrate-dsn
  db-migrate-dsn-file is not a database secret pair
  db-migrate-dsn-file is not a database secret pair (the migrate commands read db-dsn-file only)
  the bootstrap guard removed
  reset-test-db.sh guard removed
  seed-demo.sh guard removed
  reset-app-db.sh guard after its first psql
  startup parameters match their keys case-sensitively
  the migration pool opened without role=none (keys and options in the string)
  the runtime pool opened without role=none (keys and options in the string)
  the runtime pool replaces the search_path the string carries
  the string's spaces are written as plus signs
  ValidateStart reads db-migrate-dsn as set whatever it holds
  PendingMigrations lists only the versions the ledger shows when it is missing
  migrationDSN keeps whitespace
  migrate up accepts a whitespace-only db-migrate-dsn
  runtimeDSN accepts a whitespace-only db-dsn
  openDB runs the migrating boot for an unknown mode
  bootstrap-stack.sh trusts the exported MC_DB_MIGRATE_DSN
  reset-test-db.sh trusts the exported MC_DB_MIGRATE_DSN
  reset-app-db.sh trusts the exported MC_DB_MIGRATE_DSN
  seed-demo.sh trusts the exported MC_DB_MIGRATE_DSN
2026-10-23 08:01:14 -04:00

147 lines
6.3 KiB
PL/PgSQL

-- SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
-- SPDX-FileCopyrightText: 2025-2026 Christian Galo
-- mockshot-nuke.sql — exact reversal of mockshot-seed.sql, FK order.
--
-- The seed owns three predicates and nothing else: organizations whose `key`
-- starts with `mockshot_`, users whose OIDC subject starts with `mockshot:`,
-- and the catalog rows keyed `mockshot_hosting` (entitlement set) and
-- `mockshot_*` (products, and the prices hanging off them). Every other row
-- the seed created is reached from one of those three: through its
-- organization (pools, workspaces, assignments, accounts, subscriptions,
-- items, invoices, payments, grants, provisions) or through its catalog
-- parent. There is no marker column, no owner email predicate, and no name
-- match anywhere in this file.
--
-- psql -h localhost -p $POSTGRES_PORT -U member_console -d member_console \
-- -v ON_ERROR_STOP=1 -f test/mockshot-nuke.sql
BEGIN;
-- Provisions first: they reference pools, grants, subscriptions, products,
-- and the entitlement set, so every later delete depends on them being gone.
DELETE FROM core.pool_provisions
WHERE pool_id IN (
SELECT rp.pool_id FROM core.resource_pools rp
JOIN core.organizations o ON o.org_id = rp.org_id
WHERE o.key LIKE 'mockshot\_%'
);
-- The rule-change ledger of the seed's set, before the pools and the
-- organizations its effect rows reference. The seed adds its rule before any
-- pool carries the set, so in practice only the act row exists.
DELETE FROM core.entitlement_set_change_effects
WHERE change_id IN (
SELECT c.change_id FROM core.entitlement_set_changes c
JOIN core.entitlement_sets es ON es.set_id = c.set_id
WHERE es.key = 'mockshot_hosting'
);
DELETE FROM core.entitlement_set_change_obligations
WHERE change_id IN (
SELECT c.change_id FROM core.entitlement_set_changes c
JOIN core.entitlement_sets es ON es.set_id = c.set_id
WHERE es.key = 'mockshot_hosting'
);
DELETE FROM core.entitlement_set_changes
WHERE set_id IN (SELECT set_id FROM core.entitlement_sets WHERE key = 'mockshot_hosting');
DELETE FROM core.grants
WHERE granted_to_org_id IN (SELECT org_id FROM core.organizations WHERE key LIKE 'mockshot\_%');
DELETE FROM core.payments
WHERE billing_account_id IN (
SELECT a.billing_account_id FROM core.accounts a
JOIN core.organizations o ON o.org_id = a.org_id
WHERE o.key LIKE 'mockshot\_%'
);
DELETE FROM core.invoices
WHERE billing_account_id IN (
SELECT a.billing_account_id FROM core.accounts a
JOIN core.organizations o ON o.org_id = a.org_id
WHERE o.key LIKE 'mockshot\_%'
);
-- Items reference prices, so they go before the catalog below.
DELETE FROM core.subscription_items
WHERE subscription_id IN (
SELECT s.subscription_id FROM core.subscriptions s
JOIN core.accounts a ON a.billing_account_id = s.billing_account_id
JOIN core.organizations o ON o.org_id = a.org_id
WHERE o.key LIKE 'mockshot\_%'
);
DELETE FROM core.subscriptions
WHERE billing_account_id IN (
SELECT a.billing_account_id FROM core.accounts a
JOIN core.organizations o ON o.org_id = a.org_id
WHERE o.key LIKE 'mockshot\_%'
);
DELETE FROM core.accounts
WHERE org_id IN (SELECT org_id FROM core.organizations WHERE key LIKE 'mockshot\_%');
-- Workspaces and pools: the assignment joins them, so it goes first.
DELETE FROM core.pool_assignments
WHERE pool_id IN (
SELECT rp.pool_id FROM core.resource_pools rp
JOIN core.organizations o ON o.org_id = rp.org_id
WHERE o.key LIKE 'mockshot\_%'
);
DELETE FROM core.workspaces
WHERE org_id IN (SELECT org_id FROM core.organizations WHERE key LIKE 'mockshot\_%');
DELETE FROM core.resource_pools
WHERE org_id IN (SELECT org_id FROM core.organizations WHERE key LIKE 'mockshot\_%');
-- After the grants, accounts, and pools that referenced the organizations.
DELETE FROM core.organizations WHERE key LIKE 'mockshot\_%';
-- The catalog, after the subscription items and provisions that referenced
-- it: prices before their products, the rules before their set.
DELETE FROM core.prices
WHERE product_id IN (SELECT product_id FROM core.products WHERE key LIKE 'mockshot\_%');
DELETE FROM core.products WHERE key LIKE 'mockshot\_%';
-- core_writer holds no DML on core.entitlement_set_rules (migration 00018):
-- core.commit_rule_change writes a rule and nothing deletes one, so a teardown
-- deletes as the owner role. This file is a test-stack tool, run as
-- member_console, the stack's migration user. That user is the cluster's
-- bootstrap superuser, which is what lets SET ROLE core_owner and the direct
-- deletes below run; no role is a member of core_owner.
SET ROLE core_owner;
DELETE FROM core.entitlement_set_rules
WHERE set_id IN (SELECT set_id FROM core.entitlement_sets WHERE key = 'mockshot_hosting');
RESET ROLE;
DELETE FROM core.entitlement_sets WHERE key = 'mockshot_hosting';
-- The team org type is vocabulary keyed on the type itself, so it is removed
-- only once nothing is left using it — a dependency check, not a marker.
DELETE FROM core.org_types t
WHERE t.org_type = 'team'
AND NOT EXISTS (SELECT 1 FROM core.organizations o WHERE o.org_type = t.org_type);
-- Cascades the mock persons (fk_persons_user_id ON DELETE CASCADE).
DELETE FROM core.users WHERE oidc_subject LIKE 'mockshot:%';
COMMIT;
-- Should all be zero
SELECT (SELECT count(*) FROM core.users WHERE oidc_subject LIKE 'mockshot:%') AS users_left,
(SELECT count(*) FROM core.organizations
WHERE key LIKE 'mockshot\_%' AND key NOT LIKE 'mockshot\_t%') AS personal_orgs_left,
(SELECT count(*) FROM core.organizations WHERE key LIKE 'mockshot\_t%') AS team_orgs_left,
(SELECT count(*) FROM core.grants g JOIN core.organizations o
ON o.org_id = g.granted_to_org_id WHERE o.key LIKE 'mockshot\_%') AS grants_left,
(SELECT count(*) FROM core.accounts a JOIN core.organizations o
ON o.org_id = a.org_id WHERE o.key LIKE 'mockshot\_%') AS accounts_left,
(SELECT count(*) FROM core.products WHERE key LIKE 'mockshot\_%') AS products_left,
(SELECT count(*) FROM core.prices p JOIN core.products pd
ON pd.product_id = p.product_id WHERE pd.key LIKE 'mockshot\_%') AS prices_left,
(SELECT count(*) FROM core.entitlement_sets WHERE key = 'mockshot_hosting') AS entitlement_sets_left;