testutil

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Index

Constants

View Source
const PostgresRLSTestRole = "cleat_rls_test_role"

PostgresRLSTestRole is a fixed, low-privilege PostgreSQL role used by tests that must exercise real Row-Level Security enforcement rather than merely configure it.

PostgreSQL unconditionally bypasses RLS for superuser connections, and bypasses it for the owning role of a table unless that table has FORCE ROW LEVEL SECURITY set (migrations/postgres/001_schema.sql sets FORCE on all seven tenant-scoped tables, but that only closes the owner gap, not the superuser one). CLEAT_TEST_DB / CLEAT_TEST_POSTGRES conventionally point at a superuser role -- e.g. the default "postgres" role, or the POSTGRES_USER bootstrap role in the official postgres Docker image, which is also a superuser -- that then creates and therefore owns every table in SetupMinimalSchema/SetupFullSchema. A connection using that role would see RLS as a no-op regardless of how the policies are written, proving nothing about tenant isolation. Any role that is neither a superuser nor the table owner is always subject to RLS in Postgres (FORCE or not), so SetupPostgresRLSRole provisions exactly such a role, and OpenPostgresRLSTestDB opens a connection as it.

Variables

This section is empty.

Functions

func AdminDB

func AdminDB(t *testing.T, db *sql.DB, dialect Dialect) *sql.DB

AdminDB returns the handle a test should use when it needs to see or change rows without regard to which tenant owns them -- seeding a fixture for another tenant, reading a row back to check what the code under test wrote, or deleting one at teardown.

It exists because the three dialects give a test wildly different privileges by default, and only one of them says so out loud:

PostgreSQL  the test role is a superuser, and a superuser bypasses RLS
            unconditionally. Every raw read-back in the suite already runs
            exempt from the policies, silently.
MySQL       has no row-level security. Tenancy is a separate database.
SQL Server  applies its security policies to every principal, sa and dbo
            included, so the same read-back returns nothing at all.

So a dialect-generic test that wrote through a store and then read the row back on its own connection was doing two different things: on PostgreSQL it bypassed the fence, and on SQL Server it hit it. Under the hand-written test schema that never showed, because that schema had no policies. Built from the shipped migrations it shows immediately -- and in both directions. In TestCascadeDelete the unscoped DELETE matched no rows, so nothing cascaded; three of the five child-table assertions then failed, and the other two *passed*, because their rows were still there and the same policy hid them from the count.

Returning db unchanged for the two dialects that need nothing keeps the call sites free of dialect switches, and keeps the asymmetry documented in one place rather than restated at each of them.

func CleanupMSSQLTestData

func CleanupMSSQLTestData(t *testing.T, db *sql.DB)

CleanupMSSQLTestData removes all test data from the MSSQL tables. Uses DELETE with table existence checks. Order respects FK constraints.

Deletes through an administrative connection, because on a database built from the shipped migrations the tenant filter predicate applies to every principal -- sa included. A plain pool with no session context matches no rows at all, so every DELETE here removed nothing and reported no error, and the rows stayed to collide with the next test's fixtures. That was §2.71's blocker; MSSQLAdminDB returns db unchanged when the database has no policies.

func CleanupMySQLTestData

func CleanupMySQLTestData(t *testing.T, db *sql.DB)

CleanupMySQLTestData removes all test data from MySQL tables. Order respects FK constraints — child tables first.

func CleanupPostgresTestData

func CleanupPostgresTestData(t *testing.T, db *sql.DB)

CleanupPostgresTestData deletes all rows from the cleat test tables. Call before and after tests to ensure isolation from parallel tests.

func CleanupTestData

func CleanupTestData(t *testing.T, db *sql.DB, dialect Dialect, runID string)

CleanupTestData deletes test data matching the given runID pattern (e.g., "test-%") from event_history, workflow_signals, workflow_promises, concurrency_keys, idempotency_keys, workflow_update_requests, and workflow_instances.

func MSSQLAdminDB

func MSSQLAdminDB(t *testing.T, db *sql.DB) *sql.DB

MSSQLAdminDB returns a handle that can read and delete across tenants.

On a database with no security policies it returns db unchanged: there is nothing to be exempt from, and handing back a second pool would only add a connection. On a database that does enforce RLS it provisions a member of cleat_admin and returns a pool authenticated as it.

Pools are cached per DSN. Teardown runs once per test and opening a fresh pool each time would leave hundreds of them behind over a suite.

func MSSQLTestDB

func MSSQLTestDB(t *testing.T) *sql.DB

MSSQLTestDB opens a connection to the MSSQL test database. Uses CLEAT_TEST_MSSQL environment variable. Default: sqlserver://sa:CleatTest123!@localhost:1433?database=cleat

func MySQLTestDB

func MySQLTestDB(t *testing.T) *sql.DB

MySQLTestDB opens a connection to the MySQL test database. Uses CLEAT_TEST_MYSQL environment variable. Default: root:cleat@tcp(127.0.0.1:3306)/cleat

func OpenPostgresRLSTestDB

func OpenPostgresRLSTestDB(t *testing.T, superuserDB *sql.DB) *sql.DB

OpenPostgresRLSTestDB provisions PostgresRLSTestRole via superuserDB (see SetupPostgresRLSRole), then opens and returns a *separate* connection authenticated as that role. Tests that need genuine RLS enforcement -- rather than the superuser/owner bypass that superuserDB itself is subject to -- must build their WorkflowStore (or issue their raw SQL) against the returned *sql.DB, not against superuserDB. superuserDB should still be used for schema setup and any privileged cleanup.

func PostgresRLSDSN

func PostgresRLSDSN(superuserDSN string) (string, error)

PostgresRLSDSN derives a DSN for PostgresRLSTestRole from a superuser/ owner DSN (as returned by PostgresTestDSN), preserving host, port, database, and query parameters and replacing only the user info.

func PostgresTestDSN

func PostgresTestDSN() string

PostgresTestDSN resolves the PostgreSQL test DSN the same way TestDB(t, DialectPostgres) does: CLEAT_TEST_POSTGRES, falling back to CLEAT_TEST_DB, falling back to a hardcoded localhost DSN. Exported so callers that need a second, differently-privileged connection to the same test database (see OpenPostgresRLSTestDB) can derive it without duplicating the env var precedence.

func SetupFullSchema

func SetupFullSchema(t *testing.T, db *sql.DB, dialect Dialect)

SetupFullSchema is SetupMinimalSchema. It is kept because roughly forty call sites use it and the distinction it used to draw -- a subset of tables, then the rest -- is exactly the seam the two dialects duplicated themselves across. Every dialect now gets one complete schema either way.

func SetupMSSQLFullSchema

func SetupMSSQLFullSchema(t *testing.T, db *sql.DB)

SetupMSSQLFullSchema is SetupMSSQLMinimalSchema. Kept as a separate name because call sites across the repo use both; there is only ever one SQL Server test schema now, the shipped one, so the distinction the two names used to draw no longer exists.

func SetupMSSQLMinimalSchema

func SetupMSSQLMinimalSchema(t *testing.T, db *sql.DB)

SetupMSSQLMinimalSchema builds the SQL Server test schema by applying the real, shipped migrations (migrations/mssql/*.sql) via applyMigrations -- the exact code path cmd/cleat-worker/main.go runs at boot. "Minimal" is a historical name; see SetupMSSQLFullSchema.

func SetupMinimalSchema

func SetupMinimalSchema(t *testing.T, db *sql.DB, dialect Dialect)

SetupMinimalSchema builds the test schema for one dialect.

There is exactly one schema definition per dialect and this is how you reach it: the real migration file for PostgreSQL, SetupMySQLFullSchema for MySQL, SetupMSSQLFullSchema for SQL Server. "Minimal" is now a historical name -- every dialect gets its full schema, because the minimal/full split is what made the duplication possible.

It used to hold its own hand-written MySQL and SQL Server DDL, so each of those dialects had *two* independent definitions in this package plus a third in migrations/. All of them use CREATE TABLE IF NOT EXISTS against one shared test database, so whichever test ran first decided the schema for the whole package and Go's ordering decided which test that was. Four consecutive full runs against live MySQL and SQL Server produced four different failure sets, and two of the failing tests passed in isolation against the same database moments before and after failing in the suite. IMPROVEMENT-PLAN 2.60b.

The three columns where the copies had actually drifted were real defects and are fixed; collapsing to one definition is what stops the next three.

func SetupMySQLFullSchema

func SetupMySQLFullSchema(t *testing.T, db *sql.DB)

SetupMySQLFullSchema builds the MySQL test schema by applying the real, shipped migrations (migrations/mysql/*.sql) via applyMigrations -- the exact code path cmd/cleat-worker/main.go runs at boot.

This file used to hand-write ~330 lines of CREATE TABLE, a second, independent definition of a schema that already existed in migrations/mysql/. It had drifted from the shipped one in ways that hid real defects rather than being merely untidy: it was missing whole tables production has (tenants, tenant_roles, workflow_tags, workflow_routing, plugin_tables), and it declared event_history.service/operation/request NOT NULL where the shipped schema does not (migration 030 fixed that at the schema level; this file is the mechanism that let the two disagree in the first place -- IMPROVEMENT-PLAN, "the tests do not run against the schema that ships"). migration/mysql_bootstrap_test.go already proves the Runner applies these files correctly, DELIMITER handling included.

func SetupPostgresRLSRole

func SetupPostgresRLSRole(t *testing.T, db *sql.DB)

SetupPostgresRLSRole ensures PostgresRLSTestRole exists and can perform ordinary DML (SELECT/INSERT/UPDATE/DELETE) against every table in the public schema, plus EXECUTE on cleat.assert_tenant_set(), without owning any of it. Must be called with a superuser/owner connection (e.g. the one TestDB(t, DialectPostgres) returns) after the schema has been applied, so that GRANT ... ON ALL TABLES IN SCHEMA public sees every table.

It deliberately does not grant anything beyond DML: the whole point of this role is to be an ordinary, non-owning application role whose queries are actually subject to RLS.

func TestDB

func TestDB(t *testing.T, dialect Dialect) *sql.DB

TestDB opens a database connection for the given dialect using environment variables:

DialectPostgres — CLEAT_TEST_POSTGRES (fallback CLEAT_TEST_DB, then
                  postgres://localhost:5432/cleat?sslmode=disable)
DialectMySQL    — CLEAT_TEST_MYSQL (skipped if not set)
DialectMSSQL    — CLEAT_TEST_MSSQL (skipped if not set)

It creates the minimal schema and returns the connection. The test is skipped in short mode or if no database is available.

Types

type Dialect

type Dialect string

Dialect identifies the SQL dialect of a database backend.

const (
	DialectPostgres Dialect = "postgres"
	DialectMySQL    Dialect = "mysql"
	DialectMSSQL    Dialect = "mssql"
)

type PluginTestBackend

type PluginTestBackend struct {
	// Name is a human-readable backend identifier, e.g. "postgres", "mysql".
	Name string

	// Dialect identifies the SQL dialect for schema setup and migration
	// execution.
	Dialect Dialect

	// DB is the open database connection.
	DB *sql.DB

	// Cleanup releases the database connection. Must be called (typically via
	// defer) after the test completes.
	Cleanup func()
}

PluginTestBackend provides a real database connection for plugin behavioral tests. Each backend (PostgreSQL, MySQL, MSSQL) represents a database connection that a plugin can run migrations against and execute queries against. Call Cleanup when done to release the connection.

func NewPluginTestBackends

func NewPluginTestBackends(t *testing.T) []PluginTestBackend

NewPluginTestBackends returns all available database backends for plugin behavioral tests.

PostgreSQL is always attempted — a default DSN of "postgres://localhost:5432/cleat?sslmode=disable" is used when neither CLEAT_TEST_POSTGRES nor CLEAT_TEST_DB is set. If the connection fails the calling test is skipped.

MySQL is included only when CLEAT_TEST_MYSQL is set. If the variable is set but the connection fails the calling test is fatally terminated (the user explicitly requested MySQL).

MSSQL is included only when CLEAT_TEST_MSSQL is set, with the same behaviour as MySQL on connection failure.

Each backend's Cleanup function must be called (typically via defer) to release the database connection. Both PluginTestBackend.Cleanup and the test's t.Cleanup will close the connection; calling Cleanup explicitly lets tests control ordering (e.g. close after dropping test tables).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL