249 lines
11 KiB
JavaScript
249 lines
11 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { dirname, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
const read = path => readFileSync(resolve(root, path), 'utf8')
|
|
|
|
test('plugin registry api exposes status and immutable publication operations', () => {
|
|
const api = read('src/api/generator/plugin.js')
|
|
for (const name of [
|
|
'getPluginRegistryStatus',
|
|
'probePluginSqlTarget',
|
|
'getPluginDeliveryVerification',
|
|
'createPluginDeliveryRehearsal',
|
|
'listPluginDeliveryRehearsals',
|
|
'getPluginDeliveryRehearsal',
|
|
'startPluginDeliveryRehearsal',
|
|
'listPluginDeliveryRehearsalAttempts',
|
|
'getPluginDeliveryRehearsalAttempt',
|
|
'abortPluginDeliveryRehearsalAttempt',
|
|
'reconcilePluginDeliveryRehearsalAttempt',
|
|
'comparePluginDeliveryRehearsalAttempts',
|
|
'getPluginDeliveryReleaseGate',
|
|
'getPluginRegistryReadiness',
|
|
'bootstrapPluginRegistry',
|
|
'listPlugins',
|
|
'getPlugin',
|
|
'addPlugin',
|
|
'updatePlugin',
|
|
'createPluginVersion',
|
|
'publishPluginVersion',
|
|
'rollbackPluginVersion',
|
|
'preflightPluginTransition',
|
|
'executePluginTransition',
|
|
'retryPluginTransition',
|
|
'compensatePluginTransition',
|
|
'requestPluginTransitionApproval',
|
|
'listPluginTransitionApprovals',
|
|
'approvePluginTransitionApproval',
|
|
'revokePluginTransitionApproval',
|
|
'getPluginTransitionExecutions',
|
|
'reconcilePluginTransitionExecution'
|
|
]) {
|
|
assert.equal(api.includes(`export function ${name}`), true, `${name} should be exported`)
|
|
}
|
|
assert.match(api, /plugin\/registry-status/)
|
|
assert.match(api, /plugin\/sql-target\/probe/)
|
|
assert.match(api, /plugin\/delivery-verification/)
|
|
assert.match(api, /plugin\/delivery-rehearsals/)
|
|
assert.match(api, /delivery-rehearsals\/\$\{rehearsalId\}/)
|
|
assert.match(api, /delivery-rehearsals\/\$\{rehearsalId\}\/attempts/)
|
|
assert.match(api, /attempts\/\$\{attemptId\}\/abort/)
|
|
assert.match(api, /attempts\/\$\{attemptId\}\/reconcile/)
|
|
assert.match(api, /attempt-comparison/)
|
|
assert.match(api, /release-gate/)
|
|
assert.match(api, /baselineAttemptId, candidateAttemptId/)
|
|
assert.match(api, /sql-target\/probe', method: 'post'/)
|
|
assert.match(api, /plugin\/readiness/)
|
|
assert.match(api, /plugin\/bootstrap/)
|
|
assert.match(api, /versions\/\$\{pluginVersionId\}\/publish/)
|
|
assert.match(api, /versions\/\$\{pluginVersionId\}\/rollback/)
|
|
assert.match(api, /transitions\/\$\{transitionId\}\/preflight/)
|
|
assert.match(api, /params: \{ executionMode \}/)
|
|
assert.match(api, /transitions\/\$\{transitionId\}\/execute/)
|
|
assert.match(api, /transitions\/\$\{transitionId\}\/retry/)
|
|
assert.match(api, /transitions\/\$\{transitionId\}\/compensate/)
|
|
assert.match(api, /executePluginTransition\(pluginId, transitionId, data\)/)
|
|
assert.match(api, /retryPluginTransition\(pluginId, transitionId, data\)/)
|
|
assert.match(api, /compensatePluginTransition\(pluginId, transitionId, data\)/)
|
|
assert.match(api, /transitions\/\$\{transitionId\}\/approvals`/)
|
|
assert.match(api, /approvals\/\$\{approvalId\}\/approve/)
|
|
assert.match(api, /approvals\/\$\{approvalId\}\/revoke/)
|
|
assert.match(api, /executions\/\$\{executionId\}\/reconcile/)
|
|
})
|
|
|
|
test('plugin registry view manages installed definitions and release lifecycle', () => {
|
|
const view = read('src/views/generator/plugin/index.vue')
|
|
assert.match(view, /name: 'PluginRegistry'/)
|
|
assert.match(view, /registryStatus\.databaseEnabled/)
|
|
assert.match(view, /registryReadiness\.readyReleaseCount/)
|
|
assert.match(view, /bootstrapPluginRegistry/)
|
|
assert.match(view, /readinessStateLabel/)
|
|
assert.match(view, /contributionReversible/)
|
|
assert.match(view, /IRREVERSIBLE_CONTRIBUTION/)
|
|
assert.match(view, /installedManifests/)
|
|
assert.match(view, /createPluginVersion/)
|
|
assert.match(view, /publishPluginVersion/)
|
|
assert.match(view, /rollbackPluginVersion/)
|
|
assert.match(view, /detail\.transitions/)
|
|
assert.match(view, /transitionOperationLabel/)
|
|
assert.match(view, /inspectTransitionPlan/)
|
|
assert.match(view, /transitionFingerprint/)
|
|
assert.match(view, /latestExecutionStatus/)
|
|
assert.match(view, /runTransition/)
|
|
assert.match(view, /openExecutionHistory/)
|
|
assert.match(view, /executionDialogOpen/)
|
|
assert.match(view, /reconcilePluginTransitionExecution/)
|
|
assert.match(view, /reconcileExecution/)
|
|
assert.match(view, /outbox\.status/)
|
|
assert.match(view, /deliveryCount/)
|
|
assert.match(view, /leaseUntil/)
|
|
assert.match(view, /QUEUED/)
|
|
assert.match(view, /PROCESSING/)
|
|
assert.match(view, /DELIVERED/)
|
|
assert.match(view, /已入队/)
|
|
assert.match(view, /dry-run:v1/)
|
|
assert.match(view, /sql-jdbc:v1/)
|
|
assert.match(view, /permission-jdbc:v1/)
|
|
assert.match(view, /menu-jdbc:v1/)
|
|
assert.match(view, /executorCode/)
|
|
assert.match(view, /routed:v1/)
|
|
assert.match(view, /executionEnvironmentCode/)
|
|
assert.match(view, /routingFingerprint/)
|
|
assert.match(view, /approvalRequiredForNonDryRun/)
|
|
assert.match(view, /approvalSeparationEnabled/)
|
|
assert.match(view, /approvalRequestTtlMinutes/)
|
|
assert.match(view, /approvalValidityMinutes/)
|
|
assert.match(view, /realExecutorConfigured/)
|
|
assert.match(view, /executorRoutes/)
|
|
assert.match(view, /expectedTransitionFingerprint/)
|
|
assert.match(view, /expectedRoutingFingerprint/)
|
|
assert.match(view, /environmentCode/)
|
|
assert.match(view, /approvalId/)
|
|
assert.match(view, /requestPluginTransitionApproval/)
|
|
assert.match(view, /listPluginTransitionApprovals/)
|
|
assert.match(view, /approvePluginTransitionApproval/)
|
|
assert.match(view, /revokePluginTransitionApproval/)
|
|
assert.match(view, /preflightPluginTransition/)
|
|
assert.match(view, /approvalPreflight/)
|
|
assert.match(view, /executableApprovals/)
|
|
assert.match(view, /preflightStateLabel/)
|
|
assert.match(view, /approvalPreflight\.routes/)
|
|
assert.match(view, /requestReason/)
|
|
assert.match(view, /requesterRoles/)
|
|
assert.match(view, /approverRoles/)
|
|
assert.match(view, /approvalReason/)
|
|
assert.match(view, /\$prompt/)
|
|
assert.match(view, /approvedBy/)
|
|
assert.match(view, /approvedAt/)
|
|
assert.match(view, /step\.row\.executorCode/)
|
|
assert.match(view, /sqlTargetReady/)
|
|
assert.match(view, /sqlTargetEnvironmentCode/)
|
|
assert.match(view, /sqlTargetCatalog/)
|
|
assert.match(view, /probePluginSqlTarget/)
|
|
assert.match(view, /sqlTargetProbeLoading/)
|
|
assert.match(view, /sqlTargetProbe/)
|
|
assert.match(view, /probeSqlTarget/)
|
|
assert.match(view, /NOT_CHECKED/)
|
|
assert.match(view, /LEDGER_UNAVAILABLE/)
|
|
assert.match(view, /PERMISSION_LEDGER_UNAVAILABLE/)
|
|
assert.match(view, /permissionLedgerRequired/)
|
|
assert.match(view, /permissionLedgerReady/)
|
|
assert.match(view, /MENU_LEDGER_UNAVAILABLE/)
|
|
assert.match(view, /menuLedgerRequired/)
|
|
assert.match(view, /menuLedgerReady/)
|
|
assert.match(view, /INVALID_CONFIGURATION/)
|
|
assert.doesNotMatch(view, /registryStatus\.jdbcUrl/)
|
|
assert.doesNotMatch(view, /registryStatus\.username/)
|
|
assert.doesNotMatch(view, /registryStatus\.password/)
|
|
assert.match(view, /class="manifest-view"/)
|
|
assert.match(view, /generator:plugin:publish/)
|
|
assert.match(view, /generator:plugin:rollback/)
|
|
assert.match(view, /Conflicting|Manifest/)
|
|
assert.doesNotMatch(view, /<textarea/)
|
|
|
|
const runStart = view.indexOf(' runTransition(row, action)')
|
|
const runEnd = view.indexOf(' availableTransitionAction(row)', runStart)
|
|
const runTransition = view.slice(runStart, runEnd)
|
|
assert.match(runTransition, /approvalId/)
|
|
assert.match(runTransition, /preflightPluginTransition/)
|
|
assert.match(runTransition, /preflight\.approvalRequired/)
|
|
assert.doesNotMatch(runTransition, /approvalReason/)
|
|
assert.doesNotMatch(runTransition, /registryStatus\.realExecutorConfigured/)
|
|
})
|
|
|
|
test('delivery verification view exposes the routed target snapshot', () => {
|
|
const view = read('src/views/generator/delivery/index.vue')
|
|
assert.match(view, /name: 'DeliveryVerification'/)
|
|
assert.match(view, /getPluginDeliveryVerification/)
|
|
assert.match(view, /executionEnvironmentCode/)
|
|
assert.match(view, /routingFingerprint/)
|
|
assert.match(view, /executorRoutes/)
|
|
assert.match(view, /approvalRequiredForNonDryRun/)
|
|
assert.match(view, /approvalSeparationEnabled/)
|
|
assert.match(view, /approvalValidityMinutes/)
|
|
assert.match(view, /approvalRequestTtlMinutes/)
|
|
assert.match(view, /status\.checks/)
|
|
assert.match(view, /databaseRegistryEnabled/)
|
|
assert.match(view, /acceptanceCheckLabel/)
|
|
assert.match(view, /部署验收/)
|
|
assert.match(view, /permissionLedgerReady/)
|
|
assert.match(view, /menuLedgerReady/)
|
|
assert.match(view, /MENU_LEDGER_UNAVAILABLE/)
|
|
assert.match(view, /createPluginDeliveryRehearsal/)
|
|
assert.match(view, /listPluginDeliveryRehearsals/)
|
|
assert.match(view, /getPluginDeliveryRehearsal/)
|
|
assert.match(view, /rehearsalRuns/)
|
|
assert.match(view, /selectedRehearsal/)
|
|
assert.match(view, /acceptanceFingerprint/)
|
|
assert.match(view, /scenarioFingerprint/)
|
|
assert.match(view, /acceptanceSnapshot/)
|
|
assert.match(view, /requiredEvidence/)
|
|
assert.match(view, /PLAN_ONLY/)
|
|
assert.match(view, /创建验收快照/)
|
|
assert.match(view, /部署演练报告/)
|
|
assert.match(view, /startPluginDeliveryRehearsal/)
|
|
assert.match(view, /listPluginDeliveryRehearsalAttempts/)
|
|
assert.match(view, /getPluginDeliveryRehearsalAttempt/)
|
|
assert.match(view, /abortPluginDeliveryRehearsalAttempt/)
|
|
assert.match(view, /reconcilePluginDeliveryRehearsalAttempt/)
|
|
assert.match(view, /comparePluginDeliveryRehearsalAttempts/)
|
|
assert.match(view, /getPluginDeliveryReleaseGate/)
|
|
assert.match(view, /rehearsalAttempts/)
|
|
assert.match(view, /selectedAttempt/)
|
|
assert.match(view, /LOCAL_H2/)
|
|
assert.match(view, /sandboxFingerprint/)
|
|
assert.match(view, /evidenceFingerprint/)
|
|
assert.match(view, /outbox\.status/)
|
|
assert.match(view, /deliveryCount/)
|
|
assert.match(view, /lastHeartbeatAt/)
|
|
assert.match(view, /leaseUntil/)
|
|
assert.match(view, /beginAttemptPolling/)
|
|
assert.match(view, /releaseGate\.checks/)
|
|
assert.match(view, /baselineSemanticFingerprint/)
|
|
assert.match(view, /candidateSemanticFingerprint/)
|
|
assert.match(view, /发布门禁/)
|
|
assert.match(view, /语义对比/)
|
|
assert.match(view, /持久队列/)
|
|
assert.match(view, /formatEvidence/)
|
|
assert.match(view, /启动本地 H2 演练/)
|
|
assert.match(view, /本地 H2 演练证据/)
|
|
assert.doesNotMatch(view, /executePluginTransition/)
|
|
assert.doesNotMatch(view, /jdbc:h2/)
|
|
assert.doesNotMatch(view, /leaseToken/)
|
|
assert.doesNotMatch(view, /jdbcUrl/)
|
|
assert.doesNotMatch(view, /username/)
|
|
assert.doesNotMatch(view, /password/)
|
|
})
|
|
|
|
test('plugin registry menu publishes every controller permission', () => {
|
|
const sql = read('../sql/plugin_registry_menu.sql')
|
|
for (const permission of ['list', 'query', 'add', 'edit', 'version', 'publish', 'rollback', 'bootstrap', 'execute', 'approve']) {
|
|
assert.match(sql, new RegExp(`generator:plugin:${permission}`))
|
|
}
|
|
assert.match(sql, /generator\/plugin\/index/)
|
|
})
|