@aviaratech/ai-delivery 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -10
- package/dist/cli.js +9 -5
- package/dist/cli.js.map +1 -1
- package/dist/config/deliveryConfig.d.ts +20 -4
- package/dist/config/deliveryConfig.js +41 -15
- package/dist/config/deliveryConfig.js.map +1 -1
- package/dist/config/deliveryConfig.test.js +103 -2
- package/dist/config/deliveryConfig.test.js.map +1 -1
- package/dist/dispatch.js +9 -4
- package/dist/dispatch.js.map +1 -1
- package/dist/fixtures/discovery.d.ts +8 -0
- package/dist/github/client.d.ts +2 -1
- package/dist/github/client.js +50 -26
- package/dist/github/client.js.map +1 -1
- package/dist/github/commandIdentityPolicy.js +7 -2
- package/dist/github/commandIdentityPolicy.js.map +1 -1
- package/dist/lifecycle.test.js +254 -25
- package/dist/lifecycle.test.js.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/pr.d.ts +30 -1
- package/dist/pr.js +139 -19
- package/dist/pr.js.map +1 -1
- package/dist/review.d.ts +7 -0
- package/dist/review.js +47 -0
- package/dist/review.js.map +1 -1
- package/dist/review.test.js +147 -6
- package/dist/review.test.js.map +1 -1
- package/package.json +1 -1
- package/plugins/ai-delivery/.claude-plugin/plugin.json +1 -1
- package/plugins/ai-delivery/README.md +1 -1
- package/plugins/ai-delivery/skills/pr-handoff/SKILL.md +14 -9
package/dist/lifecycle.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { execFileSync, spawn } from 'node:child_process';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, readdirSync, realpathSync, rmSync, symlinkSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
4
|
+
import { cpSync, chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, readdirSync, realpathSync, rmSync, symlinkSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import { dirname, join } from 'node:path';
|
|
7
7
|
import { test, vi } from 'vitest';
|
|
@@ -17,28 +17,124 @@ import { executeTool, resumedIssueUpdate, startTrackedIssue } from './dispatch.j
|
|
|
17
17
|
import { defaultBaseRef, gitCommonDir } from './git.js';
|
|
18
18
|
import * as githubClient from './github/client.js';
|
|
19
19
|
import { createIssue, developIssue, listIssueSubissues, readyCheck, resumeCreatedIssue, updateIssue, } from './issue.js';
|
|
20
|
-
import { checkoutPr, finishIssue, listPrs, mergePr, prChecks, publishPr, submitFormalReview } from './pr.js';
|
|
20
|
+
import { checkoutPr, finishIssue, listPrs, mergePr, prChecks, prInfo, publishPr, submitFormalReview } from './pr.js';
|
|
21
21
|
import { updateIssueWorktreeDelivery } from './services/worktreeRegistry.js';
|
|
22
22
|
import { assertDeliveryRuntimeAdmitted } from './services/deliveryAdmission.js';
|
|
23
23
|
import { getDeliveryRecords } from './services/deliveryRecordService.js';
|
|
24
24
|
import { createIssuePhaseEvidence, verifyIssue } from './verification.js';
|
|
25
25
|
import { cleanupNonIssueWorktree, prepareIssueWorktree, preparePrWorktree, prepareStandaloneWorktree, } from './worktree.js';
|
|
26
|
+
const personalRoute = vi.hoisted(() => ({
|
|
27
|
+
active: false,
|
|
28
|
+
baseSha: '',
|
|
29
|
+
headSha: '',
|
|
30
|
+
reviews: [],
|
|
31
|
+
submitted: 0,
|
|
32
|
+
}));
|
|
26
33
|
vi.mock('./github/client.js', async (importOriginal) => {
|
|
27
34
|
const actual = await importOriginal();
|
|
28
35
|
return {
|
|
29
36
|
...actual,
|
|
30
|
-
createDeliveryGitHubClients: async (input) =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
createDeliveryGitHubClients: async (input) => {
|
|
38
|
+
if (personalRoute.active && input.config.roles.author.authSource === 'personal') {
|
|
39
|
+
const pr = () => ({
|
|
40
|
+
number: 23,
|
|
41
|
+
node_id: 'PR-23',
|
|
42
|
+
html_url: 'https://github.com/example/widget/pull/23',
|
|
43
|
+
head: { sha: personalRoute.headSha, ref: 'issue/17' },
|
|
44
|
+
base: { sha: personalRoute.baseSha, ref: 'main' },
|
|
45
|
+
state: 'open',
|
|
46
|
+
draft: true,
|
|
47
|
+
user: { login: 'host-user' },
|
|
48
|
+
});
|
|
49
|
+
if (input.role === 'reviewer') {
|
|
50
|
+
assert.equal(input.identity, 'synthetic-reviewer');
|
|
51
|
+
return {
|
|
52
|
+
...syntheticDiscoveryClients(),
|
|
53
|
+
authSource: 'app',
|
|
54
|
+
credentialSource: 'app:reviewer',
|
|
55
|
+
role: 'reviewer',
|
|
56
|
+
appActorLogin: async () => 'synthetic-reviewer[bot]',
|
|
57
|
+
rest: {
|
|
58
|
+
repos: { get: async () => ({ data: { full_name: 'example/widget' } }) },
|
|
59
|
+
pulls: {
|
|
60
|
+
get: async () => ({ data: pr() }),
|
|
61
|
+
listReviews: async () => ({ data: personalRoute.reviews }),
|
|
62
|
+
createReview: async (request) => {
|
|
63
|
+
assert.equal(request.event, 'APPROVE');
|
|
64
|
+
assert.equal(request.commit_id, personalRoute.headSha);
|
|
65
|
+
personalRoute.submitted += 1;
|
|
66
|
+
const review = {
|
|
67
|
+
id: 31,
|
|
68
|
+
body: request.body,
|
|
69
|
+
state: 'APPROVED',
|
|
70
|
+
commit_id: personalRoute.headSha,
|
|
71
|
+
user: { login: 'synthetic-reviewer[bot]' },
|
|
72
|
+
html_url: 'https://github.com/example/widget/pull/23#pullrequestreview-31',
|
|
73
|
+
};
|
|
74
|
+
personalRoute.reviews.push(review);
|
|
75
|
+
return { data: review };
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
graphql: async (query, variables) => query.includes('DeliveryReviewDecision')
|
|
80
|
+
? {
|
|
81
|
+
repository: {
|
|
82
|
+
pullRequest: { headRefOid: personalRoute.headSha, reviewDecision: 'REVIEW_REQUIRED' },
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
: syntheticDiscoveryClients().graphql(query, variables),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
assert.equal(input.identity, 'host-author');
|
|
89
|
+
const clients = await actual.createDeliveryGitHubClients({
|
|
90
|
+
...input,
|
|
91
|
+
env: { AUTHOR_TOKEN: 'selected-personal-token', GH_TOKEN: 'ambient-token' },
|
|
92
|
+
});
|
|
93
|
+
Object.assign(clients, {
|
|
94
|
+
authenticatedAuthor: async () => ({ actorLogin: 'host-user', credentialIdentity: 'user:37' }),
|
|
95
|
+
graphql: syntheticDiscoveryClients().graphql,
|
|
96
|
+
rest: {
|
|
97
|
+
issues: { get: async () => ({ data: { title: 'Synthetic issue' } }) },
|
|
98
|
+
git: { getRef: async () => ({ data: { object: { sha: personalRoute.baseSha } } }) },
|
|
99
|
+
pulls: {
|
|
100
|
+
list: async () => ({ data: [] }),
|
|
101
|
+
create: async () => ({ data: pr() }),
|
|
102
|
+
get: async () => ({ data: pr() }),
|
|
103
|
+
},
|
|
104
|
+
request: async () => Promise.reject(Object.assign(new Error('Rules unreadable'), { status: 403 })),
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
return clients;
|
|
108
|
+
}
|
|
109
|
+
const rest = new Octokit({
|
|
35
110
|
request: {
|
|
36
111
|
fetch: async () => {
|
|
37
112
|
throw new Error('Unexpected network request in synthetic lifecycle test.');
|
|
38
113
|
},
|
|
39
114
|
},
|
|
40
|
-
})
|
|
41
|
-
|
|
115
|
+
});
|
|
116
|
+
if (input.role === 'reviewer') {
|
|
117
|
+
Object.assign(rest.repos, { get: async () => ({ data: { full_name: 'example/widget' } }) });
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
...syntheticDiscoveryClients(),
|
|
121
|
+
authSource: input.role === 'author' && input.config.roles.author.authSource === 'personal' ? 'personal' : 'app',
|
|
122
|
+
role: input.role,
|
|
123
|
+
...(input.role === 'author'
|
|
124
|
+
? {
|
|
125
|
+
authenticatedAuthor: async () => ({
|
|
126
|
+
actorLogin: input.config.roles.author.authSource === 'personal' ? 'host-user' : 'synthetic-author[bot]',
|
|
127
|
+
credentialIdentity: input.config.roles.author.authSource === 'personal' ? 'user:37' : 'app:201',
|
|
128
|
+
}),
|
|
129
|
+
credentialSource: input.config.roles.author.authSource === 'personal' ? 'env:AUTHOR_TOKEN' : 'app:author',
|
|
130
|
+
}
|
|
131
|
+
: {}),
|
|
132
|
+
...(input.role === 'reviewer'
|
|
133
|
+
? { appActorLogin: async () => 'synthetic-reviewer', credentialSource: 'app:reviewer' }
|
|
134
|
+
: {}),
|
|
135
|
+
rest,
|
|
136
|
+
};
|
|
137
|
+
},
|
|
42
138
|
};
|
|
43
139
|
});
|
|
44
140
|
function git(root, ...args) {
|
|
@@ -100,14 +196,16 @@ async function fixture(options = {}) {
|
|
|
100
196
|
policy: { contract: 'RepositoryDeliveryPolicy@1', module: './policy.mjs' },
|
|
101
197
|
repository: 'example/widget',
|
|
102
198
|
roles: {
|
|
103
|
-
author:
|
|
104
|
-
credentialEnv: {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
199
|
+
author: options.personalAuthor
|
|
200
|
+
? { authSource: 'personal', credentialEnv: { token: 'AUTHOR_TOKEN' }, identity: 'host-author' }
|
|
201
|
+
: {
|
|
202
|
+
credentialEnv: {
|
|
203
|
+
appId: 'AUTHOR_APP_ID',
|
|
204
|
+
installationId: 'AUTHOR_INSTALLATION_ID',
|
|
205
|
+
privateKeyPath: 'AUTHOR_KEY_PATH',
|
|
206
|
+
},
|
|
207
|
+
identity: 'synthetic-author',
|
|
108
208
|
},
|
|
109
|
-
identity: 'synthetic-author',
|
|
110
|
-
},
|
|
111
209
|
reviewer: {
|
|
112
210
|
credentialEnv: {
|
|
113
211
|
appId: 'REVIEWER_APP_ID',
|
|
@@ -841,6 +939,122 @@ test('the stage output bound covers both streams and publishes no checkpoint on
|
|
|
841
939
|
rmSync(root, { recursive: true, force: true });
|
|
842
940
|
}
|
|
843
941
|
});
|
|
942
|
+
test('public PR dry run retains a configured host author and App reviewer', async () => {
|
|
943
|
+
const { root } = await fixture({ personalAuthor: true });
|
|
944
|
+
try {
|
|
945
|
+
const row = await prepareIssueWorktree({
|
|
946
|
+
baseRef: 'main',
|
|
947
|
+
identity: 'host-author',
|
|
948
|
+
issueNumber: 17,
|
|
949
|
+
repoRoot: root,
|
|
950
|
+
});
|
|
951
|
+
writeFileSync(join(row.path, 'change.txt'), 'host author change\n');
|
|
952
|
+
git(row.path, 'add', 'change.txt');
|
|
953
|
+
git(row.path, 'commit', '-qm', 'synthetic host author change');
|
|
954
|
+
await verifyIssue({ issueNumber: 17, repoRoot: row.path });
|
|
955
|
+
const result = (await executeTool('issue_pr_create', { issueNumber: 17, dryRun: true }, { repoRoot: row.path, identity: 'host-author' }));
|
|
956
|
+
assert.equal(result.dryRun, true);
|
|
957
|
+
assert.deepEqual(result.reviewRoute.author, {
|
|
958
|
+
actorLogin: 'host-user',
|
|
959
|
+
authSource: 'personal',
|
|
960
|
+
credentialSource: 'env:AUTHOR_TOKEN',
|
|
961
|
+
identity: 'host-author',
|
|
962
|
+
});
|
|
963
|
+
assert.equal(result.reviewRoute.reviewer.actorLogin, 'synthetic-reviewer');
|
|
964
|
+
assert.equal(result.reviewRoute.approvalEligibility, 'unknown');
|
|
965
|
+
}
|
|
966
|
+
finally {
|
|
967
|
+
rmSync(root, { recursive: true, force: true });
|
|
968
|
+
}
|
|
969
|
+
}, 15_000);
|
|
970
|
+
test('public dispatch publishes with selected host token and submits the configured App review once', async () => {
|
|
971
|
+
const { root, runtimeEntryPath } = await fixture({ personalAuthor: true });
|
|
972
|
+
const originalPath = process.env.PATH;
|
|
973
|
+
try {
|
|
974
|
+
const row = await prepareIssueWorktree({
|
|
975
|
+
baseRef: 'main',
|
|
976
|
+
identity: 'host-author',
|
|
977
|
+
issueNumber: 17,
|
|
978
|
+
repoRoot: root,
|
|
979
|
+
});
|
|
980
|
+
writeFileSync(join(row.path, 'change.txt'), 'host author publication\n');
|
|
981
|
+
git(row.path, 'add', 'change.txt');
|
|
982
|
+
git(row.path, 'commit', '-qm', 'synthetic host publication');
|
|
983
|
+
const run = await verifyIssue({ issueNumber: 17, repoRoot: row.path });
|
|
984
|
+
personalRoute.baseSha = run.classification.base.sha;
|
|
985
|
+
personalRoute.headSha = run.classification.head.sha;
|
|
986
|
+
personalRoute.reviews.length = 0;
|
|
987
|
+
personalRoute.submitted = 0;
|
|
988
|
+
personalRoute.active = true;
|
|
989
|
+
const realGit = (originalPath ?? '')
|
|
990
|
+
.split(':')
|
|
991
|
+
.map((directory) => join(directory, 'git'))
|
|
992
|
+
.find(existsSync);
|
|
993
|
+
if (!realGit)
|
|
994
|
+
throw new Error('Git executable unavailable for public transport test.');
|
|
995
|
+
git(root, 'config', '--local', '--unset', `url.${join(root, '.git', 'remote.git')}.insteadOf`);
|
|
996
|
+
const shimDir = join(root, '.git', 'git-shim');
|
|
997
|
+
const pushMarker = join(root, '.git', 'selected-host-push');
|
|
998
|
+
mkdirSync(shimDir);
|
|
999
|
+
const shim = join(shimDir, 'git');
|
|
1000
|
+
writeFileSync(shim, `#!/bin/sh
|
|
1001
|
+
if [ "$1" = -c ] && [ "$2" = credential.helper= ] && [ "$3" = push ]; then
|
|
1002
|
+
[ "$AI_DELIVERY_GIT_TOKEN" = selected-personal-token ] || exit 91
|
|
1003
|
+
[ "$("$GIT_ASKPASS" Password)" = selected-personal-token ] || exit 92
|
|
1004
|
+
: > "${pushMarker}"
|
|
1005
|
+
exit 0
|
|
1006
|
+
fi
|
|
1007
|
+
exec "${realGit}" "$@"
|
|
1008
|
+
`);
|
|
1009
|
+
chmodSync(shim, 0o700);
|
|
1010
|
+
process.env.PATH = `${shimDir}:${originalPath ?? ''}`;
|
|
1011
|
+
const execution = { repoRoot: row.path, runtimeEntryPath, identity: 'host-author' };
|
|
1012
|
+
const published = (await executeTool('issue_pr_create', { issueNumber: 17 }, execution));
|
|
1013
|
+
assert.equal(published.prNumber, 23);
|
|
1014
|
+
assert.equal(existsSync(pushMarker), true);
|
|
1015
|
+
assert.equal(published.reviewRoute.author.actorLogin, 'host-user');
|
|
1016
|
+
assert.equal(published.reviewRoute.author.credentialSource, 'env:AUTHOR_TOKEN');
|
|
1017
|
+
assert.equal(published.reviewRoute.reviewer.actorLogin, 'synthetic-reviewer[bot]');
|
|
1018
|
+
const info = (await executeTool('issue_pr_info', { prNumber: 23 }, execution));
|
|
1019
|
+
assert.equal(info.authorLogin, 'host-user');
|
|
1020
|
+
const artifactContent = {
|
|
1021
|
+
authorIdentity: 'host-author',
|
|
1022
|
+
checks: ['verified exact diff'],
|
|
1023
|
+
diffScopeHash: digestValue(run.classification.changedPaths),
|
|
1024
|
+
elapsedMs: 1,
|
|
1025
|
+
findings: [],
|
|
1026
|
+
head: run.classification.head,
|
|
1027
|
+
issueNumber: 17,
|
|
1028
|
+
prNumber: 23,
|
|
1029
|
+
readOnly: true,
|
|
1030
|
+
requestedEffort: 'xhigh',
|
|
1031
|
+
requestedModel: 'gpt-6-astra',
|
|
1032
|
+
effectiveEffort: 'xhigh',
|
|
1033
|
+
effectiveModel: 'gpt-6-astra',
|
|
1034
|
+
reviewerIdentity: 'synthetic-reviewer',
|
|
1035
|
+
schemaVersion: 'ai-delivery.review-artifact@1',
|
|
1036
|
+
summary: 'Independent exact-head review',
|
|
1037
|
+
verdict: 'approve',
|
|
1038
|
+
};
|
|
1039
|
+
const artifact = JSON.stringify({ ...artifactContent, artifactId: digestValue(artifactContent) });
|
|
1040
|
+
const reviewInput = { issueNumber: 17, prNumber: 23, identity: 'synthetic-reviewer', artifact };
|
|
1041
|
+
const first = (await executeTool('issue_pr_review', reviewInput, execution));
|
|
1042
|
+
assert.equal(first.reviewState.status, 'still-required');
|
|
1043
|
+
assert.equal(personalRoute.submitted, 1);
|
|
1044
|
+
const second = (await executeTool('issue_pr_review', reviewInput, execution));
|
|
1045
|
+
assert.equal(second.receiptId, first.receiptId);
|
|
1046
|
+
assert.equal(personalRoute.submitted, 1);
|
|
1047
|
+
}
|
|
1048
|
+
finally {
|
|
1049
|
+
personalRoute.active = false;
|
|
1050
|
+
personalRoute.reviews.length = 0;
|
|
1051
|
+
if (originalPath === undefined)
|
|
1052
|
+
delete process.env.PATH;
|
|
1053
|
+
else
|
|
1054
|
+
process.env.PATH = originalPath;
|
|
1055
|
+
rmSync(root, { recursive: true, force: true });
|
|
1056
|
+
}
|
|
1057
|
+
}, 15_000);
|
|
844
1058
|
test('scratch start derives safe names and reports the requested worktree', async () => {
|
|
845
1059
|
const { root, runtimeEntryPath } = await fixture();
|
|
846
1060
|
try {
|
|
@@ -916,11 +1130,7 @@ test('MCP request rejects unsupported legacy options before lifecycle dispatch',
|
|
|
916
1130
|
await server.close();
|
|
917
1131
|
}
|
|
918
1132
|
});
|
|
919
|
-
|
|
920
|
-
{ remote: 'origin', divergentOrigin: false },
|
|
921
|
-
{ remote: 'upstream', divergentOrigin: false },
|
|
922
|
-
{ remote: 'upstream', divergentOrigin: true },
|
|
923
|
-
])('synthetic lifecycle finishes with remote $remote and divergent origin $divergentOrigin', async (routing) => {
|
|
1133
|
+
async function syntheticLifecycle(routing) {
|
|
924
1134
|
const { root } = await fixture(routing);
|
|
925
1135
|
const remoteName = routing.remote;
|
|
926
1136
|
assert.equal(defaultBaseRef(root, remoteName), `refs/remotes/${remoteName}/main`);
|
|
@@ -958,6 +1168,7 @@ test.each([
|
|
|
958
1168
|
let mergeSha = '';
|
|
959
1169
|
let stalePrReadbacks = 0;
|
|
960
1170
|
let review = null;
|
|
1171
|
+
let reviewDecision = 'REVIEW_REQUIRED';
|
|
961
1172
|
const calls = [];
|
|
962
1173
|
const createPayloads = [];
|
|
963
1174
|
const statuses = { Queued: 'todo', Active: 'active', Waiting: 'blocked', Shipped: 'done' };
|
|
@@ -989,11 +1200,13 @@ test.each([
|
|
|
989
1200
|
merge_commit_sha: mergeSha || null,
|
|
990
1201
|
mergeable: true,
|
|
991
1202
|
mergeable_state: 'clean',
|
|
992
|
-
user: { login: 'synthetic-author' },
|
|
1203
|
+
user: { login: 'synthetic-author[bot]' },
|
|
993
1204
|
head: { sha: headSha, ref: 'issue/17' },
|
|
994
1205
|
base: { sha: baseSha, ref: 'main' },
|
|
995
1206
|
});
|
|
996
1207
|
const graphql = async (query, variables = {}) => {
|
|
1208
|
+
if (query.includes('DeliveryReviewDecision'))
|
|
1209
|
+
return { repository: { pullRequest: { headRefOid: headSha, reviewDecision } } };
|
|
997
1210
|
if (query.includes('ProjectDeliveryConfiguration'))
|
|
998
1211
|
return {
|
|
999
1212
|
organization: {
|
|
@@ -1128,6 +1341,10 @@ test.each([
|
|
|
1128
1341
|
};
|
|
1129
1342
|
const rest = {
|
|
1130
1343
|
request: async (route, parameters) => {
|
|
1344
|
+
if (route === 'GET /repos/{owner}/{repo}/rules/branches/{branch}')
|
|
1345
|
+
return { data: [{ type: 'pull_request', parameters: { required_approving_review_count: 1 } }] };
|
|
1346
|
+
if (route === 'GET /repos/{owner}/{repo}/branches/{branch}/protection')
|
|
1347
|
+
throw Object.assign(new Error('Synthetic protected branch visibility unavailable'), { status: 403 });
|
|
1131
1348
|
if (route === 'GET /orgs/{org}/issue-fields')
|
|
1132
1349
|
return {
|
|
1133
1350
|
data: [
|
|
@@ -1347,7 +1564,7 @@ test.each([
|
|
|
1347
1564
|
headSha = git(row.path, 'rev-parse', 'HEAD');
|
|
1348
1565
|
const run = await verifyIssue({ issueNumber, repoRoot: row.path });
|
|
1349
1566
|
await assert.rejects(publishPr(context, { issueNumber, body: 'Closes #17' }), /Delivery Impact/);
|
|
1350
|
-
await assert.rejects(publishPr(context, { issueNumber }), /Git push requires the selected author
|
|
1567
|
+
await assert.rejects(publishPr(context, { issueNumber }), /Git push requires the selected author credential token/);
|
|
1351
1568
|
// The public push requires a real author App installation token. Bind a synthetic
|
|
1352
1569
|
// publication at that external boundary so the remaining public phases run offline.
|
|
1353
1570
|
git(row.path, 'push', '-q', remoteName, `${headSha}:refs/heads/issue/17`);
|
|
@@ -1372,7 +1589,9 @@ test.each([
|
|
|
1372
1589
|
status: 'pr-published',
|
|
1373
1590
|
});
|
|
1374
1591
|
assert.equal((await listPrs(context)).pullRequests[0]?.number, prNumber);
|
|
1592
|
+
assert.equal((await prInfo(context, { prNumber })).authorLogin, 'synthetic-author[bot]');
|
|
1375
1593
|
assert.equal((await prChecks(context, prNumber)).combinedStatus, 'success');
|
|
1594
|
+
assert.equal((await prChecks(context, prNumber)).reviewState.status, 'still-required');
|
|
1376
1595
|
await assert.rejects(checkoutPr(context, prNumber), /open in-repository branch/u);
|
|
1377
1596
|
assert.equal(git(remote, 'rev-parse', 'refs/heads/issue/17'), headSha);
|
|
1378
1597
|
await assert.rejects(mergePr(context, { issueNumber, prNumber }), /submitted independent review/);
|
|
@@ -1407,8 +1626,11 @@ test.each([
|
|
|
1407
1626
|
await assert.rejects(submitFormalReview(personalReviewer, { artifact, issueNumber, prNumber }), /reviewer GitHub App role/);
|
|
1408
1627
|
const reviewResult = await submitFormalReview(reviewer, { artifact, issueNumber, prNumber });
|
|
1409
1628
|
assert.equal(reviewResult.githubReviewId, 31);
|
|
1629
|
+
assert.equal(reviewResult.reviewState.status, 'still-required');
|
|
1410
1630
|
await publishPr(context, { issueNumber, draft: false });
|
|
1411
1631
|
assert.equal(prDraft, false);
|
|
1632
|
+
await assert.rejects(mergePr(context, { issueNumber, prNumber }), /submitted APPROVED review.*still requires/u);
|
|
1633
|
+
reviewDecision = 'APPROVED';
|
|
1412
1634
|
checksPassed = false;
|
|
1413
1635
|
await assert.rejects(mergePr(context, { issueNumber, prNumber }), /failed or pending checks/);
|
|
1414
1636
|
checksPassed = true;
|
|
@@ -1434,7 +1656,9 @@ test.each([
|
|
|
1434
1656
|
const result = JSON.parse(originalResult);
|
|
1435
1657
|
const { resultId: _resultId, ...resultContent } = result;
|
|
1436
1658
|
const wrongResult = { ...resultContent, mergeSha: 'a'.repeat(40) };
|
|
1437
|
-
writeFileSync(resultPath, JSON.stringify({ ...wrongResult, resultId: digestValue(wrongResult) }), {
|
|
1659
|
+
writeFileSync(resultPath, JSON.stringify({ ...wrongResult, resultId: digestValue(wrongResult) }), {
|
|
1660
|
+
mode: 0o600,
|
|
1661
|
+
});
|
|
1438
1662
|
await assert.rejects(finishIssue(context, { issueNumber, prNumber, strategy: 'merge' }), /exact prepared merge intent/u);
|
|
1439
1663
|
writeFileSync(resultPath, originalResult, { mode: 0o600 });
|
|
1440
1664
|
const reviewedHead = headSha;
|
|
@@ -1497,5 +1721,10 @@ test.each([
|
|
|
1497
1721
|
finally {
|
|
1498
1722
|
rmSync(root, { recursive: true, force: true });
|
|
1499
1723
|
}
|
|
1500
|
-
}
|
|
1724
|
+
}
|
|
1725
|
+
test.each([
|
|
1726
|
+
{ remote: 'origin', divergentOrigin: false },
|
|
1727
|
+
{ remote: 'upstream', divergentOrigin: false },
|
|
1728
|
+
{ remote: 'upstream', divergentOrigin: true },
|
|
1729
|
+
])('synthetic lifecycle finishes with remote $remote and divergent origin $divergentOrigin', syntheticLifecycle, 15_000);
|
|
1501
1730
|
//# sourceMappingURL=lifecycle.test.js.map
|