Files
ui/tests/architecture_imports.test.ts
Oleksandr Bezdieniezhnykh c368f60853 [AZ-511] classColors carve-out to src/class-colors/ (closes F3)
Move src/features/annotations/classColors.ts to its own component directory
src/class-colors/ with a proper barrel; update the 4 consumer imports to go
through the barrel; remove the F3-pending exemption from STC-ARCH-01 and from
the architecture test fixture; clean up the 5 coupled doc/script touchpoints.
Closes baseline finding F3 and retires the 5-coupled-places carry-over surface
logged in LESSONS.md 2026-05-12.

- Add `class-colors` to scripts/check-arch-imports.mjs COMPONENT_DIRS so deep
  imports past the new barrel are caught symmetric to every other component.
- Replace the architecture test "exemption WORKS" fixture with the stronger
  "deep import into class-colors NOW FAILS" assertion (Risk 4 mitigation).
- module-layout.md: Layout Rules + Per-Component Mapping (11_class-colors,
  06_annotations, 03_shared-ui) + Verification Needed #1 + shared/class-colors
  block all updated to reflect the new home.
- 11_class-colors/description.md: Caveats §7 + Module Inventory updated.
- architecture_compliance_baseline.md: F3 marked CLOSED with full pre-resolution
  context preserved (mirrors AZ-485/F4 + AZ-486/F7 pattern); F4 carry-forward
  exemption note retired.
- 04_verification_log.md: open questions #1 + #8 marked RESOLVED.
- Build passes with no circular-import warnings (AC-4); fast suite 231/13
  skipped green (AC-5); static profile green (AC-3 — zero exemptions remain).

Batch report: _docs/03_implementation/batch_14_cycle3_report.md

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 03:08:36 +03:00

193 lines
8.0 KiB
TypeScript

import { afterEach, describe, expect, it } from 'vitest'
import { spawnSync } from 'node:child_process'
import { writeFileSync, rmSync, mkdirSync, existsSync } from 'node:fs'
import { join, resolve } from 'node:path'
// AZ-485 / F4 — verifies the STC-ARCH-01 static gate (scripts/check-arch-imports.mjs):
// - AC-5 : passes on the migrated codebase as-is
// - AC-4 : fails when a synthetic cross-component deep import is added
// - AC-4 : deep import into class-colors is NOT exempt (regression guard for
// AZ-511 — the F3 carry-over exemption was removed when classColors
// moved to src/class-colors/ with its own barrel; any consumer that
// bypasses the barrel must now fail STC-ARCH-01 like every other
// component)
// - AC-4 : ignores deep imports written inside // line comments
//
// 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/**` 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 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_NEW = `${UP2}/src/class-colors/classCo` + 'lors'
// 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(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(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('arch-imports')
expect(stderr, stderr).toBe('')
expect(status).toBe(0)
})
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(ARCH_FIXTURE_DIR, 'synthetic_deep_import.ts', body)
// Act
const { status, stderr } = runCheck('arch-imports')
// Assert
expect(status).not.toBe(0)
expect(stderr).toMatch(/STC-ARCH-01/)
expect(stderr).toMatch(/synthetic_deep_import\.ts/)
expect(stderr).toMatch(/src\/api\/client/)
})
it('AC-4: FAILS when a deep import bypasses the class-colors barrel (AZ-511 regression guard)', () => {
// Arrange — F3 was closed by AZ-511; class-colors now has a proper barrel
// at src/class-colors/index.ts, so reaching past it into the file directly
// must trip STC-ARCH-01 like every other component. The previous fixture
// asserted the exemption WORKED; this replacement asserts no exemption
// remains for class-colors at all.
const body =
`import { FALLBACK_CLASS_NAMES } ${FROM} '${DEEP_CLASSCOLORS_NEW}'\n` +
`export const _force = FALLBACK_CLASS_NAMES\n`
writeFixture(ARCH_FIXTURE_DIR, 'synthetic_classcolors_deep_import.ts', body)
// Act
const { status, stderr } = runCheck('arch-imports')
// Assert
expect(status).not.toBe(0)
expect(stderr).toMatch(/STC-ARCH-01/)
expect(stderr).toMatch(/synthetic_classcolors_deep_import\.ts/)
expect(stderr).toMatch(/src\/class-colors\/classColors/)
})
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(ARCH_FIXTURE_DIR, 'commented_out_deep_import.ts', body)
// Act
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)
})
})