feat: expand EasyCode software factory workflows
This commit is contained in:
24
RuoYi-Vue/ruoyi-ui/test/pageThemeView.test.mjs
Normal file
24
RuoYi-Vue/ruoyi-ui/test/pageThemeView.test.mjs
Normal file
@@ -0,0 +1,24 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
|
||||
const currentDir = dirname(fileURLToPath(import.meta.url))
|
||||
const source = readFileSync(resolve(currentDir, '../src/views/generator/pageTheme/index.vue'), 'utf8')
|
||||
|
||||
test('page theme admin edits frontend and admin navigation independently', () => {
|
||||
assert.match(source, /label="前台菜单"/)
|
||||
assert.match(source, /v-model="shellForm\.frontendNavigation"/)
|
||||
assert.match(source, /label="后台菜单"/)
|
||||
assert.match(source, /v-model="shellForm\.adminNavigation"/)
|
||||
assert.doesNotMatch(source, /v-model="shellForm\.navigation"/)
|
||||
})
|
||||
|
||||
test('page theme admin migrates legacy shared navigation on edit', () => {
|
||||
assert.match(source, /parseShell\(json\)/)
|
||||
assert.match(source, /raw\.frontendNavigation \|\| legacyNavigation/)
|
||||
assert.match(source, /raw\.adminNavigation \|\| legacyNavigation/)
|
||||
assert.match(source, /delete result\.navigation/)
|
||||
assert.match(source, /delete shell\.navigation/)
|
||||
})
|
||||
248
RuoYi-Vue/ruoyi-ui/test/pluginRegistryView.test.mjs
Normal file
248
RuoYi-Vue/ruoyi-ui/test/pluginRegistryView.test.mjs
Normal file
@@ -0,0 +1,248 @@
|
||||
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/)
|
||||
})
|
||||
68
RuoYi-Vue/ruoyi-ui/test/promptRegistryView.test.mjs
Normal file
68
RuoYi-Vue/ruoyi-ui/test/promptRegistryView.test.mjs
Normal file
@@ -0,0 +1,68 @@
|
||||
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('prompt registry api exposes immutable version publication operations', () => {
|
||||
const api = read('src/api/generator/prompt.js')
|
||||
for (const name of [
|
||||
'listPromptTemplates',
|
||||
'getPromptTemplate',
|
||||
'addPromptTemplate',
|
||||
'updatePromptTemplate',
|
||||
'createPromptVersion',
|
||||
'publishPromptVersion',
|
||||
'rollbackPromptVersion',
|
||||
'initializePromptV1'
|
||||
]) {
|
||||
assert.equal(api.includes(`export function ${name}`), true, `${name} should be exported`)
|
||||
}
|
||||
assert.match(api, /versions\/\$\{promptVersionId\}\/publish/)
|
||||
assert.match(api, /versions\/\$\{promptVersionId\}\/rollback/)
|
||||
})
|
||||
|
||||
test('prompt registry view manages templates and version lifecycle', () => {
|
||||
const view = read('src/views/generator/prompt/index.vue')
|
||||
assert.match(view, /name: 'PromptRegistry'/)
|
||||
assert.match(view, /class="version-drawer-body"/)
|
||||
assert.match(view, /创建草稿/)
|
||||
assert.match(view, /publishPromptVersion/)
|
||||
assert.match(view, /rollbackPromptVersion/)
|
||||
assert.match(view, /generator:prompt:publish/)
|
||||
assert.match(view, /generator:prompt:rollback/)
|
||||
assert.match(view, /contentHash/)
|
||||
assert.match(view, /currentFingerprint/)
|
||||
assert.match(view, /builtinRelease/)
|
||||
assert.match(view, /初始化 V1/)
|
||||
assert.doesNotMatch(view, /<textarea/)
|
||||
})
|
||||
|
||||
test('prompt center exposes online debugging and A/B experiment workflows', () => {
|
||||
const api = read('src/api/generator/prompt.js')
|
||||
const view = read('src/views/generator/prompt/index.vue')
|
||||
for (const name of [
|
||||
'listPromptModels',
|
||||
'debugPrompt',
|
||||
'listPromptDebugRuns',
|
||||
'scorePromptDebugRun',
|
||||
'listPromptExperiments',
|
||||
'addPromptExperiment',
|
||||
'comparePromptExperiment',
|
||||
'getPromptExperimentStats'
|
||||
]) {
|
||||
assert.equal(api.includes(`export function ${name}`), true, `${name} should be exported`)
|
||||
}
|
||||
assert.match(view, /Prompt 在线调试/)
|
||||
assert.match(view, /Prompt A\/B 测试/)
|
||||
assert.match(view, /generator:prompt:debug/)
|
||||
assert.match(view, /generator:prompt:experiment/)
|
||||
assert.match(view, /runDebug/)
|
||||
assert.match(view, /runCompare/)
|
||||
assert.match(view, /scorePromptDebugRun/)
|
||||
assert.match(view, /User Prompt 模板/)
|
||||
assert.match(view, /\{\{input\}\}/)
|
||||
})
|
||||
23
RuoYi-Vue/ruoyi-ui/test/templateBundleView.test.mjs
Normal file
23
RuoYi-Vue/ruoyi-ui/test/templateBundleView.test.mjs
Normal file
@@ -0,0 +1,23 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
|
||||
const currentDir = dirname(fileURLToPath(import.meta.url))
|
||||
const source = readFileSync(resolve(currentDir, '../src/views/generator/templateBundle/index.vue'), 'utf8')
|
||||
|
||||
test('template bundle admin page edits preview image with image upload', () => {
|
||||
assert.match(source, /prop="previewImage"/)
|
||||
assert.match(source, /<ImageUpload/)
|
||||
assert.match(source, /v-model="form\.previewImage"/)
|
||||
assert.match(source, /:limit="1"/)
|
||||
assert.match(source, /previewImage:\s*null/)
|
||||
})
|
||||
|
||||
test('template bundle admin page lists preview image thumbnail', () => {
|
||||
assert.match(source, /prop="previewImage"/)
|
||||
assert.match(source, /<el-image/)
|
||||
assert.match(source, /scope\.row\.previewImage/)
|
||||
assert.match(source, /templateBundleImageUrl/)
|
||||
})
|
||||
Reference in New Issue
Block a user