test: harden database qualification startup and cleanup evidence
MariaDB persistence qualification / mariadb (push) Failing after 7s

This commit is contained in:
cesnimda
2026-10-24 14:04:57 +02:00
parent 2b26953d6f
commit 5ac11549b2
16 changed files with 228 additions and 15 deletions
@@ -0,0 +1,32 @@
# Retained attempts and qualification-harness repairs
Application source and rebuilt artifacts remain at the security-pin checkpoint `780b9bc`.
The subsequent repairs below affect qualification tooling only. Historical acceptance reports
are unchanged. Successor counts exclude earlier attempts; no failed startup or short run becomes
a passing database, workload or PDF qualification.
| Attempt / finding | Actual outcome | Correction and evidence boundary |
| --- | --- | --- |
| Pre-pin NuGet/npm audit | Reported Pkcs/Next/Zod advisories | Compatible narrow pins in `780b9bc`; affected full-suite successors retained separately. Python findings remain OPEN. |
| First MariaDB browser invocation | One native recovery case passed; schema cleanup succeeded | Preserved under `.artifacts/p9-requal-01/attempts/harness-successor-predecessors/mariadb-browser/`. A later affected-harness successor supplies the accepted1-case result; do not add both. |
| Initial MariaDB HTTP observations | Server startup failed: relative API project path resolved against the nested config directory; zero test cases executed; schema cleanup succeeded | New nested observations/performance/Linux configs explicitly use the UI working directory, matching the existing RD21 diagnostic convention. The observations successor passes2 cases. Initial setup evidence remains under `attempts/harness-successor-predecessors/mariadb-observations/`. |
| Schema ownership before diagnostic write | Independent review identified successful CREATE followed by a failing log write could leave cleanup ownership false | Record successful CREATE ownership before writing its log. Exact old-checkpoint source replay fails the injected-error regression; corrected source passes both that case and failed-CREATE/no-DROP authority. This is offline harness proof, not an executed data-loss event. |
| Initial sustained workload setup |250 synthetic applications were created; stored canonical Career JSON omits null object properties while API DTOs include them. Exact cross-format comparison failed before preflight/timer; zero timed reads/navigations; schema cleanup succeeded | The fixture compares canonical JSON using the existing `StructuredCvProfileJson.SerializePersisted` null-property omission. Full API profile assertions and exact SQL before/after equality stay unchanged; SQL/API revision equality is added. CV override null-as-inherit behavior is untouched. Failed evidence and predecessor manifests remain under `attempts/performance-canonical-wire-failure/`. No15-minute result is inferred from it. |
| Linux runner error paths, before service execution | Review found post-browser SQL failure could retain evidence exit0, and failed log capture could suppress container removal | Browser and overall exits are separate; any post-check/cleanup error fails the runner; removal is independent of diagnostic capture; unconfirmed API removal prevents schema DROP. Two offline injected-error tests pass. No earlier Linux runtime PASS is claimed. |
The schema-ownership regression itself was debugged with offline fixture corrections for the
temporary evidence root and Docker inspect's list envelope. The retained old-source replay then
reaches the actual successful-CREATE/log-failure path and fails as expected. These development
checks are not added to backend or MariaDB runtime totals.
Initial and successor input manifests are retained. The first successor changes only the
performance web-server config among the1042 frozen runtime inputs, plus its affected wrappers;
the next changes only the two performance fixture sources. No API, UI source, built export,
Release assembly, original PDF oracle or historical test assertion changes during these repairs.
Each affected lane is rerun against a fresh generated schema. Final accepted results and hashes
belong to [validation.json](validation.json); this history does not waive a failing final gate.
Hosted843/844 retain their original missing-Docker failures. Hosted847 and849 retain expected
first-step isolation failures, with no MariaDB test execution. [Local Desktop socket recovery](LOCAL_DOCKER_RECOVERY.md)
is a separate workstation event. Neither its successful startup nor a list-only browser discovery
report establishes application acceptance.
@@ -42,3 +42,13 @@ Do not remove the blocker merely because Docker becomes available. Under the obs
The current fixture and `--existing-server` runner deliberately allow only explicit loopback TCP. A later private service transport must preserve those defaults and receive separately reviewed narrow endpoint validation; no fixture bypass is included here.
Sanitized API metadata, source/log hashes, precise blockers and next actions are in [ci-findings.json](ci-findings.json) and [CI_OPERATOR_HANDOFF.md](CI_OPERATOR_HANDOFF.md). Ignored raw files remain local; no credentials or full environment/configuration are copied into these tracked reports.
## Source checkpoint recheck
Run849/job1739 at `1a1b7c82213c24fbbc9bd4e1553a92f4c76eabd9` was inspected at11:24:57UTC.
Runner5 `Live-Runner` again prepares a host-network job with the existing merged Docker socket
bind. The first isolation guard exits1; no MariaDB test executes. The API marks downstream steps
as failed, while the raw log explicitly says the checkout/setup main steps were skipped.
The raw log SHA-256 is `4aeee7452db935ecf01afad44d2812e7fb9160239317c87d6a3d429cbdd128de`.
This independently confirms the same **BLOCKED** disposition at the published harness checkpoint;
it is neither a hosted MariaDB PASS nor an executed database-test failure.
@@ -96,7 +96,7 @@ Linux desktop browser behavior and human visual acceptance remain distinct.
## Fail-closed evidence checks
`test-reconcile.py`, `test-linux-run.py` and `mariadb-lane.py self-check` are offline harness
`test-reconcile.py`, `test-linux-run.py`, `test-mariadb-lane.py` and `mariadb-lane.py self-check` are offline harness
checks, not provider tests. `reconcile.py` reads the fixed fresh artifact paths and writes
`validation.json`; its default process fails if required gates are missing or not passing.
`--collect-only` deliberately collects partial evidence and cannot establish acceptance.
@@ -0,0 +1,38 @@
# MariaDB document-index collation repair
Finding **P9-REQUAL-DB01**, discovered during the actual MariaDB workload's preflight:
`GET /api/documents` returned500 with `Illegal mix of collations for operation 'UNION'`.
The timed workload did not start. Its250 synthetic applications and canonical profile were in
an owned generated schema; cleanup confirmed that schema absent. This is a reproduced provider
defect, separate from RD21 stale-save safety and from the earlier fixture setup errors.
The startup-created schema contains different text collations across the document name columns.
`DocumentsIndexService` combines CV names, job titles, attachment names, submitted-package names
and email subjects through seven `UNION ALL` projections. The failing query did not establish a
common name collation. The previous32-provider-case suite did not exercise this index.
Commit `2b26953d6f3c3fdacdffefa2c0a696c9084f1afd` applies explicit
`CONVERT(expression USING utf8mb4) COLLATE utf8mb4_bin` to those **read-only name projections**.
The binary collation retains the existing explicit lowercase/Nordic fold and accent-sensitive
search behavior; a general accent-insensitive collation would change that contract. Every SQL
expression is a compile-time source expression. Owner/search/filter values stay parameters.
Tenant joins, metadata/content exclusion, API response shape, stored values, table collations,
migrations and the SQLite expressions are unchanged.
Three new real-provider tests use the existing startup/version/schema guards. Before the fix,
the same three cases fail on the UNION collation error; after it they pass. They cover:
- Actual mixed startup collations and all seven source types, total/page/order agreement,
recorded metadata, excluded content and independent unchanged persisted snapshots.
- Unicode accent sensitivity, literal `%`, `_`, `!`, deterministic ties and metadata-only search.
- Owner isolation, foreign/damaged associations, foreign history exclusion and filters.
Targeted RED/GREEN reports remain in
`.artifacts/p9-requal-01/documents-provider-{red,green}/mariadb.trx` and `tests.log`.
These targeted results are not added to the full suite. The fresh full successor is **35/35**
MariaDB cases with zero failures/skips; full backend **1123/1123** also passes. The normal push
and remote equality at this source checkpoint were verified; `main` remains `5f835e54a9b74e6b71418422e28431b2af06453f`.
Independent review checked the service, controller validation, three tests and both retained TRX
reports and found no actionable issues. Broader fresh browser, workload and output successors are
reported separately in the final addendum; this repair alone is not Phase9 or production acceptance.
@@ -90,3 +90,7 @@ The [original triage](../p9-03/inherited-axe-triage.json) reconciles64 inherited
No current final verdict is locked by this source register. Root must reconcile fresh hosted CI, full regression, source binding, sustained workload and review before issuing the five independent recommendations. Open rows remain visible even if another lane passes. P9-L02 and P9-PERF-01 are low development debt; RD19/RD20 and C11 are deferred product/model scope; production-only operations stay operator gates. The 57 preservation groups are capabilities with individual residuals, not 57 confirmed defects; see [preservation](PRESERVATION.md). No test counts are summed across overlapping lanes.
## Additional verified provider defect
P9-REQUAL-DB01: actual MariaDB Documents UNION failed on mixed startup name collations. [Repair and real RED/GREEN evidence](DOCUMENTS_PROVIDER_REPAIR.md) records the narrow read-only fix, three new cases and full35/35 MariaDB plus1123/1123 backend successors. Broad final-source qualification remains separate. The register now contains58 unique findings.
@@ -6,11 +6,24 @@
"baselineSha": "549b2cba5a791862ba3f854312ea819a86dc42a0",
"giteaVersion": "1.26.0",
"apiPermissions": {
"repository": {"admin": true, "push": true, "pull": true},
"repository": {
"admin": true,
"push": true,
"pull": true
},
"userHttpStatus": 403,
"instanceRunnersHttpStatus": 403
},
"repositoryRunner": {"id": 3, "name": "pi-runner-1", "status": "offline", "labels": ["ubuntu-latest", "ubuntu-22.04"], "version": null},
"repositoryRunner": {
"id": 3,
"name": "pi-runner-1",
"status": "offline",
"labels": [
"ubuntu-latest",
"ubuntu-22.04"
],
"version": null
},
"baselineRun": {
"id": 844,
"jobId": 1734,
@@ -25,7 +38,10 @@
"mergedPrivileged": false,
"publishedPorts": [],
"sdkInstalled": "9.0.317",
"pythonGuardTests": {"passed": 5, "kind": "unit guards; mocked infrastructure"},
"pythonGuardTests": {
"passed": 5,
"kind": "unit guards; mocked infrastructure"
},
"dockerFailure": "command not found",
"dockerExitCode": 127,
"mariaDbIntegrationExecuted": false,
@@ -94,5 +110,27 @@
".artifacts/mariadb/act-runner-v0.4.1/internal_pkg_config_config.example.yaml": "e47d046dc368eb85b35665b4a5dba57ce0be4053c665ff737916a7561f7337fa",
".artifacts/mariadb/act-runner-v0.4.1/runner.go": "857b163f42aeb350aa32fbdc884e8d2ceb738d77e8a25504ac38c3e0035ab590",
".artifacts/mariadb/act-runner-v0.4.1/pkg_runner_run_context.go": "45aea88466bc40c1cddad7ee6cfe98444ffd225a719683ee319c909075466c2c"
},
"latestInspectedHostedRun": {
"observedUtc": "2026-10-24T11:24:57.184119+00:00",
"runId": 849,
"jobId": 1739,
"sourceCommit": "1a1b7c82213c24fbbc9bd4e1553a92f4c76eabd9",
"runnerId": 5,
"runnerName": "Live-Runner",
"verdict": "BLOCKED",
"actualGuardExitCode": 1,
"mariaDbExecution": false,
"mergedHostNetwork": true,
"mergedDockerSocketBind": true,
"apiStepStatusCaveat": "API marks downstream steps failure; raw log explicitly confirms checkout/setup main steps skipped. No database command executed.",
"log": {
"path": ".artifacts/p9-requal-01/hosted-849.log",
"sha256": "4aeee7452db935ecf01afad44d2812e7fb9160239317c87d6a3d429cbdd128de"
},
"apiEvidence": {
"path": ".artifacts/p9-requal-01/hosted-prepublication.json",
"sha256": "a87ecca391c7617ec05efa64776f9ff365b1afe296c76eb958e52bbfee0646a1"
}
}
}
@@ -1608,6 +1608,33 @@
"production": "RELEASE BLOCKER pending security/sidecar disposition and qualified remediation; no production fix/deployment claimed",
"debt": "open locked Python security findings and coverage gap; major ML/parser transition requires separate validated scope"
}
},
{
"id": "P9-REQUAL-DB01",
"aliases": [],
"criterion": "Phase9 REQUEST section8: actual MariaDB provider compatibility; document index must return owned metadata with preserved filters and ordering",
"source": [
{
"path": "docs/redesign/phase-9/REQUEST.md",
"section": "8 Database and persistence qualification"
}
],
"evidence": [
{
"path": "docs/redesign/phase-9/p9-requal-01/DOCUMENTS_PROVIDER_REPAIR.md",
"kind": "verified provider defect, targeted RED/GREEN, full backend and35-case provider successor"
}
],
"severityAndImpact": "High functional provider defect: Documents index returns500 on actual MariaDB startup schema; no acknowledged-save loss reproduced",
"dependency": "MariaDB document query qualification and sustained workload preflight",
"ownerAndRequiredClosure": "API DocumentsIndexService owner: narrow read-only projection fix, actual MariaDB regression including tenant/search/data preservation, fresh affected full successors",
"status": "LOCALLY REPAIRED at2b26953; targeted3/3 and full35/35 MariaDB plus1123/1123 backend PASS; broad successor checkpoint pending",
"gates": {
"phase9": "provider query repair proven; fresh complete successor qualification pending",
"phase10": "not permission to remove provider/schema/alias compatibility",
"production": "withheld: no production operation or deployed qualification",
"debt": "no schema/table collation migration required for this repair"
}
}
]
}
@@ -7,7 +7,7 @@ OUT = ROOT / '.artifacts/p9-requal-01/linux'
IMAGE = 'jobtracker-p9-requal-api:security-check'
DB = 'jobtracker-it-p9-requal-mariadb-test-1'
PIN = 'mariadb:12.2.2@sha256:42a27baaba3e8f3f0159d16381d5a7156661aa91dcc65f663cabb6a6f9503c00'
VERSION = 'p9-requal-linux-780b9bc'
VERSION = 'p9-requal-linux-2b26953'
def guard_schema(name):
if not re.fullmatch(r'jt_it_[0-9a-f]{32}', name):
@@ -71,7 +71,7 @@ def main():
'Auth__AdminEmail':'e2e@example.test','Auth__AdminPassword':'E2ePassword123!', 'Auth__AllowRegistration':'false',
'RateLimits__AuthLoginPermitLimit':'1000','Cors__Origins__0':'http://localhost:3300',
'Workers__AiOperationsEnabled':'false','Workers__SavedJobSearchAlertsEnabled':'false',
'HttpsRedirection__Enabled':'false','App__Version':VERSION,'App__CommitSha':'780b9bc',
'HttpsRedirection__Enabled':'false','App__Version':VERSION,'App__CommitSha':'2b26953',
'Data__Root':'/data','Logging__LogLevel__Default':'Warning'}
args=['docker','create','--name',api,'--label','task=p9-requal-01','--network',network,
'-p','127.0.0.1:5302:8080','--tmpfs','/data:rw,nosuid,size=512m']
@@ -2,7 +2,7 @@ import { defineConfig } from '../../../../../job-tracker-ui/node_modules/@playwr
import base from '../../../../../job-tracker-ui/playwright.config';
import path from 'node:path';
if (process.env.P9_REQUAL_LINUX_READY !== 'p9-requal-linux-780b9bc') {
if (process.env.P9_REQUAL_LINUX_READY !== 'p9-requal-linux-2b26953') {
throw new Error('Use the isolated Linux qualification runner and its verified API identity.');
}
const servers = Array.isArray(base.webServer) ? base.webServer : [base.webServer!];
@@ -12,6 +12,6 @@ export default defineConfig({ ...base,
grep: /native preview\/PDF fidelity corpus|document controls preserve settings/,
workers: 1, retries: 0,
// The runner starts and identifies the existing Linux renderer; only the UI is managed here.
webServer: [{ ...servers[1], reuseExistingServer: false }],
webServer: [{ ...servers[1], cwd: path.resolve(__dirname, '../../../../../job-tracker-ui'), reuseExistingServer: false }],
metadata: { linuxApiIdentity: process.env.P9_REQUAL_LINUX_READY, mariaDbVersion: '12.2.2' },
});
@@ -150,7 +150,10 @@ def main():
record = {'lane': lane, 'startedUtc': utc(), 'generatedSchema': schema, 'commands': [], 'exitCode': 1, 'cleanup': {'attempted': False}}
started = time.monotonic()
def execute(command, name):
nonlocal created
completed = subprocess.run(['rtk', 'proxy', *command], cwd=ROOT, env=env, capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=30)
# Cleanup ownership must survive a diagnostic write failing after successful CREATE.
if name == 'create-schema' and completed.returncode == 0: created = True
log = OUT / (lane + '-' + name + '.log')
log.write_text(scrub(completed.stdout + completed.stderr), encoding='utf-8')
record['commands'].append({'operation': name, 'exitCode': completed.returncode, 'log': str(log.relative_to(ROOT))})
@@ -172,7 +175,6 @@ def main():
record['serverVersion'] = version
if not re.fullmatch(r'12\.2\.2-MariaDB(?:-[A-Za-z0-9.]+)?', version): raise ValueError('Unexpected actual server version')
sql(schema_sql(schema), 'create-schema')
created = True
env['JOBTRACKER_MARIADB_BROWSER_CONNECTION'] = f'Server=127.0.0.1;Port=33308;Database={schema};User ID=root;Password=jt-integration-only;SslMode=None'
command = ['node', 'node_modules/@playwright/test/cli.js', 'test', '--config=' + config, '--retries=0', '--reporter=list,json', '--output=' + str(results_path)]
record['command'] = command
@@ -1,4 +1,8 @@
import { defineConfig } from '../../../../../job-tracker-ui/node_modules/@playwright/test';
import base from '../../../../../job-tracker-ui/playwright.mariadb.config';
import path from 'node:path';
export default defineConfig({ ...base, testDir: '.', testMatch: 'persistence.spec.ts', workers: 1, retries: 0 });
const cwd = path.resolve(__dirname, '../../../../../job-tracker-ui');
const servers = Array.isArray(base.webServer) ? base.webServer : [base.webServer!];
export default defineConfig({ ...base, testDir: '.', testMatch: 'persistence.spec.ts', workers: 1, retries: 0,
webServer: servers.map(server => ({ ...server, cwd })) });
@@ -5,6 +5,11 @@ import { setTimeout as sleep } from 'node:timers/promises';
export const durationMs = 15 * 60 * 1000;
export const paceMs = 500;
export function persistedCareerProfile(profile) {
// StructuredCvProfileJson.SerializePersisted omits null properties; CV overrides are a separate contract.
return JSON.parse(JSON.stringify(profile, (_key, value) => value === null ? undefined : value));
}
export function summarize(rows) {
const values = rows.filter(row => !row.error && row.status === 200).map(row => row.ms).sort((a, b) => a - b);
const percentile = p => values.length ? values[Math.ceil(values.length * p) - 1] : null;
@@ -25,6 +30,10 @@ export function assertMembership(actual, expected) {
if (process.argv.includes('--self-test')) {
assert.equal(durationMs, 900000);
assert.equal(paceMs, 500);
const profile = { contact: { fullName: 'Synthetic', phone: null }, metadata: { fields: {}, updatedAtUtc: null }, values: [null, '', 0, false] };
assert.deepEqual(persistedCareerProfile(profile), { contact: { fullName: 'Synthetic' }, metadata: { fields: {} }, values: [null, '', 0, false] });
assert.equal(profile.contact.phone, null);
assert.notDeepEqual(persistedCareerProfile(profile), persistedCareerProfile({ ...profile, contact: { fullName: 'Changed' } }));
assert.deepEqual(summarize([]), { attempts: 0, failures: 0, p50Ms: null, p95Ms: null, p99Ms: null, maxMs: null });
const rows = Array.from({ length: 100 }, (_, i) => ({ ms: i + 1, status: 200 }));
assert.deepEqual(summarize([...rows, { ms: 999, status: 500 }, { ms: 999, error: 'timeout' }]),
@@ -8,7 +8,10 @@ if (process.env.PLAYWRIGHT_STATIC_EXPORT !== 'true' || process.env.PLAYWRIGHT_PR
}
const artifacts = path.resolve('../.artifacts/p9-requal-01/performance');
const cwd = path.resolve(__dirname, '../../../../../job-tracker-ui');
const servers = Array.isArray(base.webServer) ? base.webServer : [base.webServer!];
export default defineConfig({ ...base, testDir: '.', testMatch: 'sustained.spec.ts', workers: 1, retries: 0,
webServer: servers.map(server => ({ ...server, cwd })),
timeout: 18 * 60 * 1000, outputDir: path.join(artifacts, 'test-results'),
reporter: [['list'], ['json', { outputFile: path.join(artifacts, 'playwright-results.json') }]],
});
@@ -1,7 +1,7 @@
import { test, expect, type APIRequestContext } from '../../../../../job-tracker-ui/node_modules/@playwright/test';
import os from 'node:os';
import { performance } from 'node:perf_hooks';
import { durationMs, paceMs, waitUntil, summarize, assertMembership } from './metrics.mjs';
import { durationMs, paceMs, waitUntil, summarize, assertMembership, persistedCareerProfile } from './metrics.mjs';
import { snapshot, sql } from './mariadb-proof.mjs';
const api = 'http://localhost:5302/api';
@@ -53,7 +53,8 @@ test('P9-REQUAL-01 real MariaDB: 15 minutes, 250 applications, two paced readers
const initialProfile = await (await page.request.get(`${api}/career/profile`)).json();
expect((await page.request.put(`${api}/career/profile`, { headers: owner.headers,
data: { expectedVersion: initialProfile.revision, profile } })).status()).toBe(200);
const savedProfile = (await (await page.request.get(`${api}/career/profile`)).json()).profile;
const savedResponse = await (await page.request.get(`${api}/career/profile`)).json();
const savedProfile = savedResponse.profile;
expect(savedProfile.jobs).toHaveLength(8);
expect(savedProfile.jobs.map((job: { start: string; end: string; bullets: string[] }) => ({ start: job.start, end: job.end, bullets: job.bullets })))
.toEqual(profile.jobs.map(job => ({ start: job.start, end: job.end, bullets: job.bullets })));
@@ -62,10 +63,11 @@ test('P9-REQUAL-01 real MariaDB: 15 minutes, 250 applications, two paced readers
expect(cv.status()).toBe(200);
const cvId = (await cv.json()).id;
const before = await snapshot(owner.id);
evidence.before = before;
assertMembership(before.ids, ids);
expect(before.ownerCount).toBe(250); expect(before.deletedCount).toBe(0);
expect(JSON.parse(before.profile)).toEqual(savedProfile);
evidence.before = before;
expect(before.revision).toBe(savedResponse.revision);
expect(JSON.parse(before.profile)).toEqual(persistedCareerProfile(savedProfile));
await Promise.all(readers.map(reader => login(reader, email)));
await page.addInitScript(() => localStorage.setItem('uiLanguage', 'en'));
await context.route('**/*', route => {
@@ -120,7 +120,7 @@ def collect_trx(name, file, expected):
def main():
results = {'backend':collect_trx('backend','backend/backend.trx',1123),
'mariadb':collect_trx('mariadb','mariadb/mariadb.trx',32)}
'mariadb':collect_trx('mariadb','mariadb/mariadb.trx',35)}
for index in range(1,5): results[f'ordinary-{index}'] = collect_browser(f'ordinary-{index}')
try:
if not all(results[f'ordinary-{i}']['status']=='PASS' for i in range(1,5)): raise ValueError('Incomplete ordinary shards.')
@@ -0,0 +1,44 @@
"""Offline ownership regression; no server or browser is started."""
import contextlib, importlib.util, io, json, tempfile, unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
spec=importlib.util.spec_from_file_location('lane',Path(__file__).with_name('mariadb-lane.py'))
lane=importlib.util.module_from_spec(spec); spec.loader.exec_module(lane)
class SchemaOwnership(unittest.TestCase):
def exercise(self,create_code):
queries=[]
fixture={'Name':'/'+lane.CONTAINER,'Id':'task-id','Image':'image-id',
'Config':{'Image':lane.IMAGE,'Labels':{'com.docker.compose.project':'jobtracker-it-p9-requal','com.docker.compose.service':'mariadb-test'}},
'State':{'Running':True},'NetworkSettings':{'Ports':{'3306/tcp':[{'HostIp':'127.0.0.1','HostPort':'33308'}]}},
'HostConfig':{'Tmpfs':{'/var/lib/mysql':''}},'Mounts':[]}
def run(command,**kwargs):
if command[2:4]==['docker','inspect']: return SimpleNamespace(returncode=0,stdout=json.dumps([fixture]),stderr='')
query=next(value[len('--execute='):] for value in command if value.startswith('--execute='))
queries.append(query)
code=create_code if query.startswith('CREATE DATABASE') else 0
output='12.2.2-MariaDB-ubu2404' if 'VERSION()' in query else '0'
return SimpleNamespace(returncode=code,stdout=output,stderr='')
original=Path.write_text
def write(path,*args,**kwargs):
if create_code==0 and path.name.endswith('-create-schema.log'): raise OSError('Synthetic log-write failure')
return original(path,*args,**kwargs)
socket=MagicMock(); socket.__enter__.return_value.connect_ex.return_value=1
with tempfile.TemporaryDirectory(prefix='p9-schema-owner-',dir=lane.ROOT/'.artifacts/p9-requal-01') as directory:
out=Path(directory)
self.assertTrue(out.resolve().is_relative_to(lane.ROOT/'.artifacts/p9-requal-01'))
with patch.object(lane,'OUT',out), patch.object(lane.subprocess,'run',run), patch.object(lane.socket,'socket',return_value=socket), \
patch.object(Path,'write_text',write), patch.object(lane.sys,'argv',['mariadb-lane.py','mariadb-observations']), \
patch.object(lane.subprocess,'Popen',side_effect=AssertionError('No browser after setup failure')), contextlib.redirect_stdout(io.StringIO()):
code=lane.main()
self.assertEqual(code,1)
record=json.loads((out/'mariadb-observations-exit.json').read_text(encoding='utf-8'))
self.assertEqual(any(query.startswith('DROP DATABASE') for query in queries),create_code==0,record)
self.assertEqual(record['cleanup'].get('schemaAbsent',False),create_code==0)
def test_created_schema_is_owned_before_log_write(self): self.exercise(0)
def test_failed_create_never_authorizes_drop(self): self.exercise(1)
if __name__=='__main__': unittest.main()