待测试
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$documents = @(Get-ChildItem -LiteralPath $root -File -Filter '*.openapi.json')
|
||||
if ($documents.Count -ne 1) { throw 'Exactly one current OpenAPI JSON export is required at the repository root' }
|
||||
$document = Get-Content -Raw -Encoding UTF8 -LiteralPath $documents[0].FullName | ConvertFrom-Json
|
||||
|
||||
$operations = @(
|
||||
foreach ($pathProperty in $document.paths.PSObject.Properties) {
|
||||
foreach ($methodProperty in $pathProperty.Value.PSObject.Properties) {
|
||||
if ($methodProperty.Name -in @('get', 'post', 'put', 'delete', 'patch')) {
|
||||
[PSCustomObject]@{ Path = $pathProperty.Name; Method = $methodProperty.Name }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
if ($operations.Count -ne 149) { throw "Current OpenAPI operation count drifted: $($operations.Count)" }
|
||||
|
||||
function Get-Operation {
|
||||
param([string]$Path, [string]$Method)
|
||||
$pathProperty = $document.paths.PSObject.Properties[$Path]
|
||||
if (-not $pathProperty) { throw "Missing path: $Path" }
|
||||
$operation = $pathProperty.Value.PSObject.Properties[$Method]
|
||||
if (-not $operation) { throw "Missing operation: $Method $Path" }
|
||||
return $operation.Value
|
||||
}
|
||||
|
||||
$videoList = Get-Operation -Path '/genealogy/app/genealogies/{genealogyId}/videos' -Method 'post'
|
||||
if ($videoList.requestBody.content.'application/json'.schema.'$ref' -ne '#/components/schemas/VideoBody') {
|
||||
throw 'Video create must consume VideoBody'
|
||||
}
|
||||
$videoBody = $document.components.schemas.VideoBody
|
||||
if ((@($videoBody.required) -join ',') -ne 'videoTitle,videoOssId') {
|
||||
throw 'VideoBody required fields drifted'
|
||||
}
|
||||
if ($videoList.responses.'200'.content.'application/json'.schema) {
|
||||
throw 'Video create unexpectedly gained a response DTO; review F10 integration'
|
||||
}
|
||||
$videoPage = Get-Content -Raw -Encoding UTF8 -LiteralPath (Join-Path $root 'pages/family/f10-video-list.vue')
|
||||
foreach ($token in @('pickAndUploadVideo', 'appApi.createVideo', 'videoTitle', 'videoOssId: receipt.value.ossId')) {
|
||||
if (-not $videoPage.Contains($token)) { throw "F10 video publish contract missing: $token" }
|
||||
}
|
||||
foreach ($forbidden in @('v-model="form.videoOssId"', 'v-model="form.status"', 'v-model="form.durationSeconds"')) {
|
||||
if ($videoPage.Contains($forbidden)) { throw "F10 must not expose auto or management field: $forbidden" }
|
||||
}
|
||||
|
||||
foreach ($path in @('/genealogy/app/site/articles', '/genealogy/app/site/pages/{pageKey}')) {
|
||||
$operation = Get-Operation -Path $path -Method 'get'
|
||||
if ($operation.responses.'200'.content.'application/json'.schema) {
|
||||
throw "Site content response DTO changed for $path; review M10 integration"
|
||||
}
|
||||
}
|
||||
|
||||
$auditDocs = @(Get-ChildItem -LiteralPath (Join-Path $root 'docs') -File -Filter '*149*.md')
|
||||
if ($auditDocs.Count -lt 1) { throw 'Current 149 operation audit document is missing' }
|
||||
|
||||
Write-Output 'CURRENT-OPENAPI-INVENTORY-CONTRACT PASS'
|
||||
@@ -1,8 +1,9 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$json = Get-Content -Raw -Encoding UTF8 -LiteralPath (Join-Path $root 'APP.openapi.json') | ConvertFrom-Json
|
||||
$yaml = Get-Content -Raw -Encoding UTF8 -LiteralPath (Join-Path $root 'APP.openapi.yaml')
|
||||
$documents = @(Get-ChildItem -LiteralPath $root -File -Filter '*.openapi.json')
|
||||
if ($documents.Count -ne 1) { throw 'Exactly one current OpenAPI JSON export is required at the repository root' }
|
||||
$json = Get-Content -Raw -Encoding UTF8 -LiteralPath $documents[0].FullName | ConvertFrom-Json
|
||||
|
||||
$path = '/genealogy/app/feedback'
|
||||
$pathProperty = $json.paths.PSObject.Properties[$path]
|
||||
@@ -33,17 +34,8 @@ foreach ($field in $fields) {
|
||||
throw "FeedbackBody.$field must be a string"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($yamlFact in @(
|
||||
' /genealogy/app/feedback:',
|
||||
"`$ref: '#/components/schemas/FeedbackBody'",
|
||||
' FeedbackBody:',
|
||||
' - feedbackContent',
|
||||
' feedbackType:',
|
||||
' feedbackContent:',
|
||||
' contactInfo:'
|
||||
)) {
|
||||
if (-not $yaml.Contains($yamlFact)) { throw "Feedback YAML fact is missing: $yamlFact" }
|
||||
if ((@($body.properties.feedbackType.enum) -join ',') -ne 'advice,bug,complaint,other') {
|
||||
throw 'FeedbackBody.feedbackType must use advice,bug,complaint,other'
|
||||
}
|
||||
|
||||
Write-Output 'FEEDBACK-OPENAPI-CONTRACT PASS'
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("assert");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const toDataModuleUrl = (source) =>
|
||||
`data:text/javascript;base64,${Buffer.from(source).toString("base64")}`;
|
||||
|
||||
const run = async () => {
|
||||
const rawSource = fs.readFileSync(path.join(__dirname, "../utils/api.js"), "utf8");
|
||||
const moduleBody = rawSource.slice(rawSource.indexOf("const successCodes"));
|
||||
const prelude = `
|
||||
const currentUser = {};
|
||||
const genealogies = [];
|
||||
const publicGenealogies = [];
|
||||
const notifications = [];
|
||||
const joinApplications = [];
|
||||
const listFamilyFeedFixtures = () => [];
|
||||
const listFamilyArticleFixtures = () => [];
|
||||
const listFamilyAlbumFixtures = () => [];
|
||||
const listCeremonyFixtures = () => [];
|
||||
const listGrowthRecordFixtures = () => [];
|
||||
const listNotificationFixtures = () => [];
|
||||
const runtimeConfig = { baseUrl: "https://backend-api.ddxcjp.cn", clientId: "client-1", tenantId: "000000" };
|
||||
const hasRemoteConfig = () => true;
|
||||
const resolveRuntimeMode = () => "remote";
|
||||
const AUTH_VERIFICATION_OPERATION = Object.freeze({});
|
||||
const assertSmsCode = (value) => value;
|
||||
const GENEALOGY_ACCESS_PRESET = Object.freeze({ MEMBER_ONLY: "MEMBER_ONLY" });
|
||||
const fromApiGenealogyAccess = () => null;
|
||||
const session = { getToken: () => "session-1", saveToken() {} };
|
||||
`;
|
||||
const requests = [];
|
||||
globalThis.uni = {
|
||||
request(options) {
|
||||
requests.push(options);
|
||||
queueMicrotask(() => options.success({ statusCode: 200, data: { code: 200, data: {} } }));
|
||||
return { abort() {} };
|
||||
},
|
||||
};
|
||||
const { appApi } = await import(toDataModuleUrl(`${prelude}\n${moduleBody}`));
|
||||
|
||||
await appApi.updateProfile({ sex: "2" });
|
||||
assert.strictEqual(requests.at(-1).data.sex, "2");
|
||||
await assert.rejects(appApi.updateProfile({ sex: "男" }), /sex 必须为 0、1 或 2/);
|
||||
|
||||
await appApi.submitFeedback({ feedbackType: "bug", feedbackContent: "保存时出现错误" });
|
||||
assert.strictEqual(requests.at(-1).data.feedbackType, "bug");
|
||||
await assert.rejects(
|
||||
appApi.submitFeedback({ feedbackType: "功能问题", feedbackContent: "保存时出现错误" }),
|
||||
/feedbackType 必须为 advice、bug、complaint 或 other/,
|
||||
);
|
||||
|
||||
delete globalThis.uni;
|
||||
process.stdout.write("FORM-ENUM-API-RUNTIME-SMOKE PASS\n");
|
||||
};
|
||||
|
||||
run().catch((error) => {
|
||||
process.stderr.write(`${error.stack || error.message}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -17,9 +17,10 @@ foreach ($page in @($g08, $g09, $g10)) {
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($required in @('appApi.applyToJoin', 'createRequestController', 'applicantName', 'phone', 'relationDesc', 'applyReason', 'inviterUserId')) {
|
||||
foreach ($required in @('appApi.applyToJoin', 'createRequestController', 'applicantName', 'phone', 'relationDesc', 'applyReason')) {
|
||||
Assert-Contains $g08 $required "G08 must submit the APP join-application contract field: $required"
|
||||
}
|
||||
if ($g08 -match [regex]::Escape('inviterUserId')) { throw 'G08 must not expose a raw inviter user ID without a candidate selector' }
|
||||
foreach ($retired in @('findGenealogyFixture', 'joinSamples', 'LOCAL_WITHDRAWN', 'applicationSamples')) {
|
||||
if ($g08 -match [regex]::Escape($retired)) { throw "G08 must not retain local application fixture state: $retired" }
|
||||
}
|
||||
|
||||
@@ -58,7 +58,14 @@ if (($avatar.type -join ',') -ne 'string,null' -or $avatar.pattern -ne '^[1-9][0
|
||||
foreach ($field in @('birthLunar', 'deathLunar', 'personStatus', 'sex')) {
|
||||
$schema = $body.properties.$field
|
||||
if ($schema.type -ne 'string') { throw "LineagePersonBody.$field must be a dictionary string" }
|
||||
if ($schema.enum) { throw "LineagePersonBody.$field enum changed; update the T-series selector contract" }
|
||||
}
|
||||
|
||||
if ((@($body.properties.sex.enum) -join ',') -ne '0,1,2') { throw 'LineagePersonBody.sex must use 0,1,2' }
|
||||
if ((@($body.properties.birthLunar.enum) -join ',') -ne '0,1') { throw 'LineagePersonBody.birthLunar must use 0,1' }
|
||||
if ((@($body.properties.deathLunar.enum) -join ',') -ne '0,1') { throw 'LineagePersonBody.deathLunar must use 0,1' }
|
||||
if ((@($body.properties.personStatus.enum) -join ',') -ne '0,1,2') { throw 'LineagePersonBody.personStatus must use 0,1,2' }
|
||||
if ((@($body.required) -notcontains 'bindingMode' -or (@($body.properties.bindingMode.enum) -join ',') -ne 'NONE,SELF,SPECIFIED')) {
|
||||
throw 'LineagePersonBody.bindingMode must be required and use NONE,SELF,SPECIFIED'
|
||||
}
|
||||
|
||||
Write-Output 'LINEAGE-OPENAPI-CONTRACT PASS'
|
||||
|
||||
@@ -27,7 +27,12 @@ foreach ($required in @(
|
||||
'baseline.value = submittedSnapshot',
|
||||
'requestController.abort()',
|
||||
'pageActive = false',
|
||||
':disabled="feedbackState === ''submitting''"'
|
||||
':disabled="feedbackState === ''submitting''"',
|
||||
'value: "bug"',
|
||||
'value: "advice"',
|
||||
'value: "complaint"',
|
||||
'value: "other"',
|
||||
'@click="selectFeedbackType(type.value)"'
|
||||
)) {
|
||||
if (-not $page.Contains($required)) { throw "M07 contract is missing: $required" }
|
||||
}
|
||||
|
||||
@@ -51,13 +51,47 @@ const run = async () => {
|
||||
assert.strictEqual(requests.at(-1).data.coverOssId, ossId);
|
||||
await appApi.createAlbumPhoto("1001", "2001", { ossId });
|
||||
assert.strictEqual(requests.at(-1).data.ossId, ossId);
|
||||
await appApi.createVideo("1001", {
|
||||
videoTitle: "清明祭祖",
|
||||
videoDesc: "家族活动记录",
|
||||
videoOssId: ossId,
|
||||
});
|
||||
assert.deepStrictEqual(requests.at(-1).data, {
|
||||
videoTitle: "清明祭祖",
|
||||
videoDesc: "家族活动记录",
|
||||
videoOssId: ossId,
|
||||
});
|
||||
await appApi.createCeremony("1001", { ceremonyType: "祭祖", ceremonyTitle: "清明祭祖", coverOssId: ossId });
|
||||
assert.strictEqual(requests.at(-1).data.coverOssId, ossId);
|
||||
await appApi.createCeremony("1001", {
|
||||
ceremonyType: "祭祖",
|
||||
ceremonyTitle: "清明祭祖",
|
||||
locationAddress: "北京市朝阳区测试路",
|
||||
longitude: "116.4074",
|
||||
latitude: "39.9042",
|
||||
status: "0",
|
||||
});
|
||||
assert.deepStrictEqual(requests.at(-1).data, {
|
||||
ceremonyType: "祭祖",
|
||||
ceremonyTitle: "清明祭祖",
|
||||
locationAddress: "北京市朝阳区测试路",
|
||||
longitude: 116.4074,
|
||||
latitude: 39.9042,
|
||||
status: "0",
|
||||
});
|
||||
await assert.rejects(
|
||||
appApi.createCeremony("1001", { ceremonyType: "祭祖", ceremonyTitle: "清明祭祖", longitude: 116.4074 }),
|
||||
/longitude 和 latitude 必须同时提供/,
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
appApi.createAlbumPhoto("1001", "2001", { ossId: 900001 }),
|
||||
/OSS ID 字符串/,
|
||||
);
|
||||
await assert.rejects(
|
||||
appApi.createVideo("1001", { videoTitle: "测试视频", videoOssId: 900001 }),
|
||||
/OSS ID 字符串/,
|
||||
);
|
||||
|
||||
delete globalThis.uni;
|
||||
process.stdout.write("OSS-ID-PAYLOAD-API-RUNTIME-SMOKE PASS\n");
|
||||
|
||||
@@ -30,6 +30,12 @@ foreach ($required in @('appApi.getProfile', 'onShow(loadProfile)', 'profile.nic
|
||||
foreach ($required in @('appApi.getProfile', 'appApi.updateProfile', 'pickAndUploadImage', 'form.nickName', 'form.realName', 'form.sex', 'form.birthday', 'form.email', 'avatarId', 'buildUpdatePayload')) {
|
||||
Require-Text $m02 $required 'M02'
|
||||
}
|
||||
foreach ($required in @('const sexOptions', 'value: "0"', 'value: "1"', 'value: "2"', 'range-key="label"', '@change="changeSex"')) {
|
||||
Require-Text $m02 $required 'M02'
|
||||
}
|
||||
foreach ($forbidden in @('readonly-value', 'form-row--readonly')) {
|
||||
Forbid-Text $m02 $forbidden 'M02'
|
||||
}
|
||||
foreach ($forbidden in @('avatarOssId', 'coverOssId', 'fileId')) {
|
||||
Forbid-Text $m02 $forbidden 'M02'
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ function Read-Page([string]$path) { Get-Content -LiteralPath (Join-Path $root $p
|
||||
$contracts = [ordered]@{
|
||||
'pages/records/r03-gift-list.vue' = @('appApi.getRelativeRecords', 'relativeId', 'openPage("R04"', 'record-list')
|
||||
'pages/records/r04-gift-editor.vue' = @('appApi.createRelativeRecord', 'relativeName', 'mediaOssIds', 'pickAndUploadImage')
|
||||
'pages/records/r05-ritual-list.vue' = @('appApi.getCeremonies', 'ceremonyId', 'openPage("R06"', 'openPage("R07"')
|
||||
'pages/records/r05-ritual-list.vue' = @('appApi.getCeremonies', 'ceremonyId', 'openPage(', '"R06"', '"R07"')
|
||||
'pages/records/r06-ritual-detail.vue' = @('appApi.getCeremonyDetail', 'ceremonyId')
|
||||
'pages/records/r07-ritual-editor.vue' = @('appApi.createCeremony', 'ceremonyType', 'ceremonyTitle')
|
||||
'pages/records/r07-ritual-editor.vue' = @('appApi.createCeremony', 'ceremonyType', 'ceremonyTitle', 'locationAddress')
|
||||
'pages/records/r08-growth-journal.vue' = @('appApi.getGrowthRecords', 'appApi.createGrowthRecord', 'recordId', 'recordTitle', 'view.value = "list"')
|
||||
'pages/records/r09-life-events.vue' = @('hasValidContext', 'returnToRecords')
|
||||
'pages/records/r10-memo-list.vue' = @('appApi.getMemos', 'appApi.createMemo', 'memoId', 'memoTitle', 'view.value = "list"')
|
||||
@@ -51,4 +51,9 @@ foreach ($forbidden in @('lifeEvents', 'createLifeEvent', 'saveLifeEvent', 'grow
|
||||
if ($r09.Contains($forbidden)) { throw "R09 must remain closed without a backend contract: $forbidden" }
|
||||
}
|
||||
|
||||
$r07 = Read-Page 'pages/records/r07-ritual-editor.vue'
|
||||
foreach ($forbidden in @('form.longitude', 'form.latitude')) {
|
||||
if ($r07.Contains($forbidden)) { throw "R07 must not expose raw map coordinates without a map picker: $forbidden" }
|
||||
}
|
||||
|
||||
Write-Output 'R-BUSINESS-FLOW-CONTRACT PASS'
|
||||
|
||||
Reference in New Issue
Block a user