mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 08:11:10 +00:00
[AZ-486] F7 endpoint builders + STC-ARCH-02 (cycle 1 close)
Single source of truth for every /api/<service>/... URL the UI talks to: src/api/endpoints.ts (25 typed builders) re-exported via the F4 barrel. Migrates 13 production callsites in admin / annotations / flights / settings / dataset / auth / api-client / FlightContext / DetectionClasses to endpoints.* . Adds the STC-ARCH-02 static gate (--mode=api-literals in scripts/check-arch-imports.mjs, wired into scripts/run-tests.sh) that fails any new hardcoded /api/<service>/ literal in src/ outside endpoints.ts and *.test.tsx? files. Tests: +36 contract assertions in src/api/endpoints.test.ts (every builder, character-identical), +6 STC-ARCH-02 architecture cases in tests/architecture_imports.test.ts (single / double / template literal fail paths, *.test.* exemption, line-comment skip, migrated codebase pass). Fast profile 167 -> 209 PASS / 13 SKIP / 0 FAIL, +42 new, 0 regressions. Static profile 31 / 31 PASS. Closes architecture baseline finding F7. Cycle 1 of Phase B closed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,45 +9,64 @@ import { join, resolve } from 'node:path'
|
||||
// - AC-4 : ignores the F3-pending exemption (features/annotations/classColors)
|
||||
// - AC-4 : ignores deep imports written inside // line comments
|
||||
//
|
||||
// Exercises the actual gate via subprocess so a regression in the script
|
||||
// AZ-486 / F7 — verifies the STC-ARCH-02 static gate (same script,
|
||||
// --mode=api-literals):
|
||||
// - AC-4 : passes on the migrated codebase as-is
|
||||
// - AC-3 : fails when a synthetic `/api/<service>/...` literal is added
|
||||
// (covers single-quoted, double-quoted, and template-literal forms)
|
||||
// - AC-3 : *.test.ts colocated under src/ are exempt (the test file IS the
|
||||
// contract per module-layout.md "code-derived documentation")
|
||||
// - AC-3 : literals inside // line comments do not trip the gate
|
||||
//
|
||||
// Both gates are exercised via subprocess so a regression in the script
|
||||
// (regex drift, exemption logic, exit code) trips the test even if the bash
|
||||
// glue in run-tests.sh keeps reporting PASS.
|
||||
//
|
||||
// All offending substrings are built via concatenation at runtime so the
|
||||
// scanner itself does not flag this test file when it walks `tests/**`.
|
||||
// scanner itself does not flag this test file when it walks `tests/**` or
|
||||
// `src/**` (api-literals mode); the api-literals scanner does walk `src/**`
|
||||
// but exempts `*.test.tsx?` so colocated test files are safe.
|
||||
|
||||
const REPO_ROOT = resolve(__dirname, '..')
|
||||
const SCRIPT = join(REPO_ROOT, 'scripts', 'check-arch-imports.mjs')
|
||||
const FIXTURE_DIR = join(REPO_ROOT, 'tests', '_arch_fixtures')
|
||||
const ARCH_FIXTURE_DIR = join(REPO_ROOT, 'tests', '_arch_fixtures')
|
||||
const API_FIXTURE_DIR = join(REPO_ROOT, 'src', '_arch_fixtures')
|
||||
|
||||
const FROM = 'fr' + 'om'
|
||||
const UP2 = '..' + '/..'
|
||||
const DEEP_API = `${UP2}/src/api/cl` + 'ient'
|
||||
const DEEP_CLASSCOLORS = `${UP2}/src/features/annotations/classCo` + 'lors'
|
||||
|
||||
function runCheck(): { status: number; stderr: string } {
|
||||
const res = spawnSync('node', [SCRIPT, `--root=${REPO_ROOT}`], {
|
||||
cwd: REPO_ROOT,
|
||||
encoding: 'utf8',
|
||||
})
|
||||
// Build synthetic API path strings by concatenation so this test file itself
|
||||
// never matches the api-literal regex when scanned. Quote characters are
|
||||
// added per fixture body below.
|
||||
const API_ADMIN_USERS = '/api/' + 'admin/' + 'users/me'
|
||||
const API_FLIGHTS = '/api/' + 'flights/' + 'liveGps'
|
||||
|
||||
function runCheck(mode: 'arch-imports' | 'api-literals'): { status: number; stderr: string } {
|
||||
const res = spawnSync(
|
||||
'node',
|
||||
[SCRIPT, `--root=${REPO_ROOT}`, `--mode=${mode}`],
|
||||
{ cwd: REPO_ROOT, encoding: 'utf8' },
|
||||
)
|
||||
return { status: res.status ?? -1, stderr: res.stderr ?? '' }
|
||||
}
|
||||
|
||||
function writeFixture(filename: string, content: string): string {
|
||||
mkdirSync(FIXTURE_DIR, { recursive: true })
|
||||
const path = join(FIXTURE_DIR, filename)
|
||||
function writeFixture(dir: string, filename: string, content: string): string {
|
||||
mkdirSync(dir, { recursive: true })
|
||||
const path = join(dir, filename)
|
||||
writeFileSync(path, content, 'utf8')
|
||||
return path
|
||||
}
|
||||
|
||||
describe('AZ-485 STC-ARCH-01 — no cross-component deep imports', () => {
|
||||
afterEach(() => {
|
||||
if (existsSync(FIXTURE_DIR)) rmSync(FIXTURE_DIR, { recursive: true, force: true })
|
||||
if (existsSync(ARCH_FIXTURE_DIR)) rmSync(ARCH_FIXTURE_DIR, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
it('AC-5: passes on the migrated codebase (no fixtures)', () => {
|
||||
// Assert
|
||||
const { status, stderr } = runCheck()
|
||||
const { status, stderr } = runCheck('arch-imports')
|
||||
expect(stderr, stderr).toBe('')
|
||||
expect(status).toBe(0)
|
||||
})
|
||||
@@ -55,9 +74,9 @@ describe('AZ-485 STC-ARCH-01 — no cross-component deep imports', () => {
|
||||
it('AC-4: FAILS when a deep import into another component is introduced', () => {
|
||||
// Arrange
|
||||
const body = `import { api } ${FROM} '${DEEP_API}'\nexport const _force = api\n`
|
||||
writeFixture('synthetic_deep_import.ts', body)
|
||||
writeFixture(ARCH_FIXTURE_DIR, 'synthetic_deep_import.ts', body)
|
||||
// Act
|
||||
const { status, stderr } = runCheck()
|
||||
const { status, stderr } = runCheck('arch-imports')
|
||||
// Assert
|
||||
expect(status).not.toBe(0)
|
||||
expect(stderr).toMatch(/STC-ARCH-01/)
|
||||
@@ -70,9 +89,9 @@ describe('AZ-485 STC-ARCH-01 — no cross-component deep imports', () => {
|
||||
const body =
|
||||
`import { FALLBACK_CLASS_NAMES } ${FROM} '${DEEP_CLASSCOLORS}'\n` +
|
||||
`export const _force = FALLBACK_CLASS_NAMES\n`
|
||||
writeFixture('classcolors_exemption.ts', body)
|
||||
writeFixture(ARCH_FIXTURE_DIR, 'classcolors_exemption.ts', body)
|
||||
// Act
|
||||
const { status, stderr } = runCheck()
|
||||
const { status, stderr } = runCheck('arch-imports')
|
||||
// Assert
|
||||
expect(stderr, stderr).toBe('')
|
||||
expect(status).toBe(0)
|
||||
@@ -81,9 +100,82 @@ describe('AZ-485 STC-ARCH-01 — no cross-component deep imports', () => {
|
||||
it('AC-4: deep imports inside line comments do not trip the gate', () => {
|
||||
// Arrange
|
||||
const body = `// import { api } ${FROM} '${DEEP_API}'\nexport const _x = 1\n`
|
||||
writeFixture('commented_out_deep_import.ts', body)
|
||||
writeFixture(ARCH_FIXTURE_DIR, 'commented_out_deep_import.ts', body)
|
||||
// Act
|
||||
const { status } = runCheck()
|
||||
const { status } = runCheck('arch-imports')
|
||||
// Assert
|
||||
expect(status).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('AZ-486 STC-ARCH-02 — no hardcoded /api/<service>/ literals', () => {
|
||||
afterEach(() => {
|
||||
if (existsSync(API_FIXTURE_DIR)) rmSync(API_FIXTURE_DIR, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
it('AC-4: passes on the migrated codebase (no fixtures)', () => {
|
||||
// Assert
|
||||
const { status, stderr } = runCheck('api-literals')
|
||||
expect(stderr, stderr).toBe('')
|
||||
expect(status).toBe(0)
|
||||
})
|
||||
|
||||
it('AC-3: FAILS when a single-quoted /api/<service>/ literal is added in src/', () => {
|
||||
// Arrange
|
||||
const body = `export const url = '${API_ADMIN_USERS}'\n`
|
||||
writeFixture(API_FIXTURE_DIR, 'synthetic_api_literal_single.ts', body)
|
||||
// Act
|
||||
const { status, stderr } = runCheck('api-literals')
|
||||
// Assert
|
||||
expect(status).not.toBe(0)
|
||||
expect(stderr).toMatch(/STC-ARCH-02/)
|
||||
expect(stderr).toMatch(/synthetic_api_literal_single\.ts/)
|
||||
})
|
||||
|
||||
it('AC-3: FAILS when a double-quoted /api/<service>/ literal is added in src/', () => {
|
||||
// Arrange
|
||||
const body = `export const url = "${API_ADMIN_USERS}"\n`
|
||||
writeFixture(API_FIXTURE_DIR, 'synthetic_api_literal_double.ts', body)
|
||||
// Act
|
||||
const { status, stderr } = runCheck('api-literals')
|
||||
// Assert
|
||||
expect(status).not.toBe(0)
|
||||
expect(stderr).toMatch(/STC-ARCH-02/)
|
||||
expect(stderr).toMatch(/synthetic_api_literal_double\.ts/)
|
||||
})
|
||||
|
||||
it('AC-3: FAILS when a template-literal /api/<service>/ form is added in src/', () => {
|
||||
// Arrange — backticks built via String.fromCharCode(96) to keep this test
|
||||
// file itself a non-matching artifact under api-literals scanning of src/
|
||||
// (the test file lives under tests/, but defense in depth is cheap).
|
||||
const bt = String.fromCharCode(96)
|
||||
const body = `export const url = (id: string) => ${bt}${API_FLIGHTS}/\${id}${bt}\n`
|
||||
writeFixture(API_FIXTURE_DIR, 'synthetic_api_literal_template.ts', body)
|
||||
// Act
|
||||
const { status, stderr } = runCheck('api-literals')
|
||||
// Assert
|
||||
expect(status).not.toBe(0)
|
||||
expect(stderr).toMatch(/STC-ARCH-02/)
|
||||
expect(stderr).toMatch(/synthetic_api_literal_template\.ts/)
|
||||
})
|
||||
|
||||
it('AC-3: still PASSES when an offending literal lives in a *.test.ts file under src/', () => {
|
||||
// Arrange — test files are exempt: the test file IS the contract
|
||||
const body = `export const url = '${API_ADMIN_USERS}'\n`
|
||||
writeFixture(API_FIXTURE_DIR, 'synthetic_api_literal_exempt.test.ts', body)
|
||||
// Act
|
||||
const { status, stderr } = runCheck('api-literals')
|
||||
// Assert
|
||||
expect(stderr, stderr).toBe('')
|
||||
expect(status).toBe(0)
|
||||
})
|
||||
|
||||
it('AC-3: literals inside // line comments do not trip the gate', () => {
|
||||
// Arrange
|
||||
const body = `// example: '${API_ADMIN_USERS}'\nexport const _x = 1\n`
|
||||
writeFixture(API_FIXTURE_DIR, 'synthetic_api_literal_commented.ts', body)
|
||||
// Act
|
||||
const { status } = runCheck('api-literals')
|
||||
// Assert
|
||||
expect(status).toBe(0)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user