mirror of
https://github.com/NomaDamas/k-skill.git
synced 2026-06-24 02:04:11 +00:00
Pin delivery-tracking doc samples to checked fixtures
The review follow-up showed the docs regression only proved that the two markdown copies matched each other. This change adds a checked-in fixture for the verified CJ and 우체국 public outputs, then requires both docs to match that fixture exactly while scanning the full parsed samples for TEL, phone-like strings, and raw sensitive field names. Constraint: Must keep CI offline while tying docs to verified smoke-test invoices Constraint: Existing PR #13 already publishes dated CJ and 우체국 public samples Rejected: Keep shape-only assertions | shared-but-wrong markdown drift would still pass CI Confidence: high Scope-risk: narrow Directive: Refresh scripts/fixtures/delivery-tracking-public-samples.json only after rerunning live smoke verification for the documented invoices Tested: node --test scripts/skill-docs.test.js; npm run ci; python3 /tmp/cj_verify.py; npx --yes skills add . --list Not-tested: python3 /tmp/epost_verify.py (service.epost.go.kr connection timed out repeatedly on 2026-03-27 from this environment)
This commit is contained in:
parent
5543509969
commit
cfa76eccf3
2 changed files with 77 additions and 52 deletions
51
scripts/fixtures/delivery-tracking-public-samples.json
Normal file
51
scripts/fixtures/delivery-tracking-public-samples.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"cj": {
|
||||
"carrier": "cj",
|
||||
"invoice": "1234567890",
|
||||
"status_code": "91",
|
||||
"status": "배달완료",
|
||||
"timestamp": "2026-03-21 12:22:13",
|
||||
"location": "경기광주오포",
|
||||
"event_count": 3,
|
||||
"recent_events": [
|
||||
{
|
||||
"timestamp": "2026-03-10 03:01:45",
|
||||
"location": "청원HUB",
|
||||
"status_code": "44",
|
||||
"status": "상품이동중"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-03-21 10:53:19",
|
||||
"location": "경기광주오포",
|
||||
"status_code": "82",
|
||||
"status": "배송출발"
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-03-21 12:22:13",
|
||||
"location": "경기광주오포",
|
||||
"status_code": "91",
|
||||
"status": "배달완료"
|
||||
}
|
||||
]
|
||||
},
|
||||
"epost": {
|
||||
"carrier": "epost",
|
||||
"invoice": "1234567890123",
|
||||
"status": "배달완료",
|
||||
"timestamp": "2025.12.04 15:13",
|
||||
"location": "제주우편집중국",
|
||||
"event_count": 2,
|
||||
"recent_events": [
|
||||
{
|
||||
"timestamp": "2025.12.04 15:13",
|
||||
"location": "제주우편집중국",
|
||||
"status": "배달준비"
|
||||
},
|
||||
{
|
||||
"timestamp": "2025.12.04 15:13",
|
||||
"location": "제주우편집중국",
|
||||
"status": "배달완료"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,10 @@ function read(relativePath) {
|
|||
return fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
|
||||
}
|
||||
|
||||
function readJson(relativePath) {
|
||||
return JSON.parse(read(relativePath));
|
||||
}
|
||||
|
||||
function extractQuotedEntries(block, indent) {
|
||||
return block
|
||||
.split("\n")
|
||||
|
|
@ -43,6 +47,21 @@ function findJsonFenceAfterLabel(doc, label) {
|
|||
return JSON.parse(match[1]);
|
||||
}
|
||||
|
||||
function assertSanitizedPublicOutput(output, label) {
|
||||
const serialized = JSON.stringify(output);
|
||||
|
||||
assert.doesNotMatch(serialized, /\bTEL\b/i, `${label} must not leak TEL fragments`);
|
||||
assert.doesNotMatch(
|
||||
serialized,
|
||||
/\d{2,4}[.\-]\d{3,4}[.\-]\d{4}/,
|
||||
`${label} must not leak phone-number-like strings anywhere in the published sample`,
|
||||
);
|
||||
assert.doesNotMatch(serialized, /crgNm/, `${label} must not leak CJ assignee/source fields`);
|
||||
assert.doesNotMatch(serialized, /sender/i, `${label} must not leak sender fields`);
|
||||
assert.doesNotMatch(serialized, /receiver/i, `${label} must not leak receiver fields`);
|
||||
assert.doesNotMatch(serialized, /delivered_to/i, `${label} must not leak delivered_to fields`);
|
||||
}
|
||||
|
||||
test("root npm test script includes the skill docs regression suite", () => {
|
||||
const packageJson = JSON.parse(read("package.json"));
|
||||
|
||||
|
|
@ -339,6 +358,9 @@ test("delivery-tracking published examples lock a shared normalized non-PII sche
|
|||
});
|
||||
|
||||
test("delivery-tracking docs publish aligned sample normalized outputs for both carriers", () => {
|
||||
const expectedSamples = readJson(
|
||||
path.join("scripts", "fixtures", "delivery-tracking-public-samples.json"),
|
||||
);
|
||||
const skill = read(path.join("delivery-tracking", "SKILL.md"));
|
||||
const featureDoc = read(path.join("docs", "features", "delivery-tracking.md"));
|
||||
const cjSkillOutput = findJsonFenceAfterLabel(skill, "CJ 공개 출력 예시");
|
||||
|
|
@ -348,56 +370,8 @@ test("delivery-tracking docs publish aligned sample normalized outputs for both
|
|||
|
||||
assert.deepEqual(cjSkillOutput, cjFeatureOutput, "CJ sample output must stay aligned across docs");
|
||||
assert.deepEqual(epostSkillOutput, epostFeatureOutput, "ePost sample output must stay aligned across docs");
|
||||
|
||||
assert.deepEqual(Object.keys(cjSkillOutput), [
|
||||
"carrier",
|
||||
"invoice",
|
||||
"status_code",
|
||||
"status",
|
||||
"timestamp",
|
||||
"location",
|
||||
"event_count",
|
||||
"recent_events",
|
||||
]);
|
||||
assert.equal(cjSkillOutput.carrier, "cj");
|
||||
assert.equal(cjSkillOutput.invoice, "1234567890");
|
||||
assert.equal(typeof cjSkillOutput.status, "string");
|
||||
assert.equal(typeof cjSkillOutput.timestamp, "string");
|
||||
assert.equal(typeof cjSkillOutput.location, "string");
|
||||
assert.equal(typeof cjSkillOutput.event_count, "number");
|
||||
assert.ok(Array.isArray(cjSkillOutput.recent_events));
|
||||
assert.ok(cjSkillOutput.recent_events.length > 0 && cjSkillOutput.recent_events.length <= 3);
|
||||
for (const event of cjSkillOutput.recent_events) {
|
||||
assert.deepEqual(Object.keys(event), ["timestamp", "location", "status_code", "status"]);
|
||||
}
|
||||
|
||||
assert.deepEqual(Object.keys(epostSkillOutput), [
|
||||
"carrier",
|
||||
"invoice",
|
||||
"status",
|
||||
"timestamp",
|
||||
"location",
|
||||
"event_count",
|
||||
"recent_events",
|
||||
]);
|
||||
assert.equal(epostSkillOutput.carrier, "epost");
|
||||
assert.equal(epostSkillOutput.invoice, "1234567890123");
|
||||
assert.equal(typeof epostSkillOutput.status, "string");
|
||||
assert.equal(typeof epostSkillOutput.timestamp, "string");
|
||||
assert.equal(typeof epostSkillOutput.location, "string");
|
||||
assert.equal(typeof epostSkillOutput.event_count, "number");
|
||||
assert.ok(Array.isArray(epostSkillOutput.recent_events));
|
||||
assert.ok(epostSkillOutput.recent_events.length > 0 && epostSkillOutput.recent_events.length <= 3);
|
||||
for (const event of epostSkillOutput.recent_events) {
|
||||
assert.deepEqual(Object.keys(event), ["timestamp", "location", "status"]);
|
||||
assert.ok(!JSON.stringify(event).includes("TEL"));
|
||||
}
|
||||
|
||||
for (const output of [cjSkillOutput, epostSkillOutput]) {
|
||||
const serialized = JSON.stringify(output);
|
||||
assert.ok(!serialized.includes("crgNm"));
|
||||
assert.ok(!serialized.includes("sender"));
|
||||
assert.ok(!serialized.includes("receiver"));
|
||||
assert.ok(!serialized.includes("delivered_to"));
|
||||
}
|
||||
assert.deepEqual(cjSkillOutput, expectedSamples.cj, "CJ sample output must stay pinned to the verified public fixture");
|
||||
assert.deepEqual(epostSkillOutput, expectedSamples.epost, "ePost sample output must stay pinned to the verified public fixture");
|
||||
assertSanitizedPublicOutput(cjSkillOutput, "CJ sample output");
|
||||
assertSanitizedPublicOutput(epostSkillOutput, "ePost sample output");
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue