Skip to content

Commit 6feff10

Browse files
committed
test(release): validate native workflow evidence refs
1 parent 820fb22 commit 6feff10

2 files changed

Lines changed: 82 additions & 2 deletions

File tree

scripts/record-native-workflow-evidence.mjs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ const writeJson = async (repoRoot, relativePath, value) =>
1919
const getToday = () => new Date().toISOString().slice(0, 10);
2020

2121
const isExternalEvidenceLink = (value) => /^https?:\/\//.test(value);
22+
const githubActionsRunPattern =
23+
/^https:\/\/github\.com\/[^/]+\/[^/]+\/actions\/runs\/(\d+)(?:\/.*)?$/;
24+
const gitCommitPattern = /^[0-9a-f]{6,40}$/i;
25+
26+
const getGithubActionsRunId = (value) =>
27+
value.match(githubActionsRunPattern)?.[1];
2228

2329
const pathExists = async (repoRoot, relativePath) => {
2430
try {
@@ -82,12 +88,24 @@ const assertRequired = (options) => {
8288
}
8389
};
8490

91+
const assertWorkflowReference = ({ commit, runUrl }) => {
92+
if (!gitCommitPattern.test(commit)) {
93+
throw new Error("--commit must be a short or full git commit SHA");
94+
}
95+
96+
if (!getGithubActionsRunId(runUrl)) {
97+
throw new Error("--run-url must be a GitHub Actions run URL");
98+
}
99+
};
100+
85101
const assertArtifactEvidenceExists = async ({
86102
androidArtifact,
87103
iosArtifact,
88-
repoRoot
104+
repoRoot,
105+
runUrl
89106
}) => {
90107
const missing = [];
108+
const runId = getGithubActionsRunId(runUrl);
91109

92110
for (const artifact of [iosArtifact, androidArtifact]) {
93111
if (
@@ -105,6 +123,26 @@ const assertArtifactEvidenceExists = async ({
105123
)}`
106124
);
107125
}
126+
127+
const mismatchedArtifacts = [iosArtifact, androidArtifact].filter(
128+
(artifact) => {
129+
if (!isExternalEvidenceLink(artifact)) {
130+
return false;
131+
}
132+
133+
const artifactRunId = getGithubActionsRunId(artifact);
134+
135+
return artifactRunId && artifactRunId !== runId;
136+
}
137+
);
138+
139+
if (mismatchedArtifacts.length > 0) {
140+
throw new Error(
141+
`GitHub artifact URLs must belong to ${runUrl}: ${mismatchedArtifacts.join(
142+
", "
143+
)}`
144+
);
145+
}
108146
};
109147

110148
export const listNativeWorkflowEvidence = async ({
@@ -136,10 +174,12 @@ export const recordNativeWorkflowEvidence = async ({
136174
runUrl
137175
};
138176
assertRequired(options);
177+
assertWorkflowReference({ commit, runUrl });
139178
await assertArtifactEvidenceExists({
140179
androidArtifact,
141180
iosArtifact,
142-
repoRoot
181+
repoRoot,
182+
runUrl
143183
});
144184

145185
const manifest = await readJson(repoRoot, manifestPath);

scripts/record-native-workflow-evidence.test.mjs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,46 @@ describe("native workflow evidence recorder", () => {
5858
).rejects.toThrow("--android-artifact, --ios-artifact required");
5959
});
6060

61+
it("requires a git commit SHA and GitHub Actions run URL", async () => {
62+
await expect(
63+
recordNativeWorkflowEvidence({
64+
androidArtifact:
65+
"https://github.com/example/repo/actions/runs/1/artifacts/android",
66+
commit: "not a sha",
67+
iosArtifact:
68+
"https://github.com/example/repo/actions/runs/1/artifacts/ios",
69+
repoRoot,
70+
runUrl: "https://github.com/example/repo/actions/runs/1"
71+
})
72+
).rejects.toThrow("--commit must be a short or full git commit SHA");
73+
74+
await expect(
75+
recordNativeWorkflowEvidence({
76+
androidArtifact:
77+
"https://github.com/example/repo/actions/runs/1/artifacts/android",
78+
commit: "abc123",
79+
iosArtifact:
80+
"https://github.com/example/repo/actions/runs/1/artifacts/ios",
81+
repoRoot,
82+
runUrl: "https://example.test/native-run"
83+
})
84+
).rejects.toThrow("--run-url must be a GitHub Actions run URL");
85+
});
86+
87+
it("requires GitHub artifact URLs to belong to the recorded run", async () => {
88+
await expect(
89+
recordNativeWorkflowEvidence({
90+
androidArtifact:
91+
"https://github.com/example/repo/actions/runs/2/artifacts/android",
92+
commit: "abc123",
93+
iosArtifact:
94+
"https://github.com/example/repo/actions/runs/1/artifacts/ios",
95+
repoRoot,
96+
runUrl: "https://github.com/example/repo/actions/runs/1"
97+
})
98+
).rejects.toThrow("GitHub artifact URLs must belong");
99+
});
100+
61101
it("records a green workflow run without mutating during dry-run", async () => {
62102
const tempRepo = await createTempRepo();
63103
const result = await recordNativeWorkflowEvidence({

0 commit comments

Comments
 (0)