Skip to content

Commit cd4ab8e

Browse files
bgagentclaude
andcommitted
test(linear-feedback): rewrite tests against OAuth context signature
Wave C migrated postIssueComment / addIssueReaction / reportIssueFailure from a (secretArn: string, ...) signature to a (ctx: LinearFeedbackContext, ...) signature, but the test file still passed bare strings — TypeScript caught it at compile time only when CI ran a full build. Three test suites failed to compile (the typecheck error blocked the whole suite, not just this file). - Mock `resolveLinearOauthToken` (the new resolver) instead of `getLinearSecret` (the old PAK fetcher). - Build a `LinearFeedbackContext` fixture with linearWorkspaceId + registryTableName, pass it everywhere SECRET_ARN was used. - Update the Authorization-header assertion to match the new `Bearer <token>` form (PAK was bare-token; OAuth is Bearer-prefixed). All 41 tests across linear-feedback, linear-webhook-processor, and orchestrate-task-feedback pass locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 13e18c8 commit cd4ab8e

1 file changed

Lines changed: 33 additions & 23 deletions

File tree

cdk/test/handlers/shared/linear-feedback.test.ts

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
* SOFTWARE.
1818
*/
1919

20-
const getLinearSecretMock = jest.fn();
21-
jest.mock('../../../src/handlers/shared/linear-verify', () => ({
22-
getLinearSecret: (...args: unknown[]) => getLinearSecretMock(...args),
20+
const resolveLinearOauthTokenMock = jest.fn();
21+
jest.mock('../../../src/handlers/shared/linear-oauth-resolver', () => ({
22+
resolveLinearOauthToken: (...args: unknown[]) => resolveLinearOauthTokenMock(...args),
2323
}));
2424

2525
const fetchMock = jest.fn();
@@ -28,13 +28,17 @@ const fetchMock = jest.fn();
2828

2929
import {
3030
addIssueReaction,
31+
type LinearFeedbackContext,
3132
postIssueComment,
3233
reportIssueFailure,
3334
} from '../../../src/handlers/shared/linear-feedback';
3435

35-
const SECRET_ARN = 'arn:aws:secretsmanager:us-east-1:123:secret:bgagent/linear/api-token-XYZ';
36+
const CTX: LinearFeedbackContext = {
37+
linearWorkspaceId: 'ws-uuid-1',
38+
registryTableName: 'TestLinearWorkspaceRegistry',
39+
};
3640
const ISSUE_ID = 'issue-1';
37-
const TOKEN = 'lin_api_TESTTOKEN';
41+
const TOKEN = 'lin_oauth_TESTTOKEN';
3842

3943
function jsonResponse(body: unknown, status: number = 200): Response {
4044
return {
@@ -46,34 +50,40 @@ function jsonResponse(body: unknown, status: number = 200): Response {
4650

4751
describe('linear-feedback', () => {
4852
beforeEach(() => {
49-
getLinearSecretMock.mockReset();
53+
resolveLinearOauthTokenMock.mockReset();
5054
fetchMock.mockReset();
51-
getLinearSecretMock.mockResolvedValue(TOKEN);
55+
resolveLinearOauthTokenMock.mockResolvedValue({
56+
accessToken: TOKEN,
57+
scope: 'read write',
58+
workspaceSlug: 'acme',
59+
oauthSecretArn: 'arn:secret:acme',
60+
});
5261
fetchMock.mockResolvedValue(jsonResponse({ data: { commentCreate: { success: true } } }));
5362
});
5463

5564
describe('postIssueComment', () => {
5665
test('POSTs the commentCreate mutation with the issue id and body', async () => {
57-
const ok = await postIssueComment(SECRET_ARN, ISSUE_ID, '❌ blocked');
66+
const ok = await postIssueComment(CTX, ISSUE_ID, '❌ blocked');
5867

5968
expect(ok).toBe(true);
6069
expect(fetchMock).toHaveBeenCalledTimes(1);
6170
const [url, init] = fetchMock.mock.calls[0];
6271
expect(url).toBe('https://api.linear.app/graphql');
6372
expect(init.method).toBe('POST');
6473
expect(init.headers).toMatchObject({
65-
'Authorization': TOKEN,
74+
// OAuth tokens use Bearer prefix per Phase 2.0b-O2.
75+
'Authorization': `Bearer ${TOKEN}`,
6676
'Content-Type': 'application/json',
6777
});
6878
const body = JSON.parse(init.body as string) as { query: string; variables: Record<string, string> };
6979
expect(body.query).toContain('commentCreate');
7080
expect(body.variables).toEqual({ issueId: ISSUE_ID, body: '❌ blocked' });
7181
});
7282

73-
test('returns false (and logs warn) when the secret cannot be resolved', async () => {
74-
getLinearSecretMock.mockResolvedValueOnce(null);
83+
test('returns false (and logs warn) when the token cannot be resolved', async () => {
84+
resolveLinearOauthTokenMock.mockResolvedValueOnce(null);
7585

76-
const ok = await postIssueComment(SECRET_ARN, ISSUE_ID, 'msg');
86+
const ok = await postIssueComment(CTX, ISSUE_ID, 'msg');
7787

7888
expect(ok).toBe(false);
7989
expect(fetchMock).not.toHaveBeenCalled();
@@ -82,31 +92,31 @@ describe('linear-feedback', () => {
8292
test('returns false on non-2xx response (no throw)', async () => {
8393
fetchMock.mockResolvedValueOnce(jsonResponse({}, 500));
8494

85-
const ok = await postIssueComment(SECRET_ARN, ISSUE_ID, 'msg');
95+
const ok = await postIssueComment(CTX, ISSUE_ID, 'msg');
8696

8797
expect(ok).toBe(false);
8898
});
8999

90100
test('returns false on GraphQL errors (no throw)', async () => {
91101
fetchMock.mockResolvedValueOnce(jsonResponse({ errors: [{ message: 'auth' }] }));
92102

93-
const ok = await postIssueComment(SECRET_ARN, ISSUE_ID, 'msg');
103+
const ok = await postIssueComment(CTX, ISSUE_ID, 'msg');
94104

95105
expect(ok).toBe(false);
96106
});
97107

98108
test('returns false on network failure (swallowed)', async () => {
99109
fetchMock.mockRejectedValueOnce(new Error('ECONNRESET'));
100110

101-
const ok = await postIssueComment(SECRET_ARN, ISSUE_ID, 'msg');
111+
const ok = await postIssueComment(CTX, ISSUE_ID, 'msg');
102112

103113
expect(ok).toBe(false);
104114
});
105115

106-
test('returns false when getLinearSecret throws (swallowed at resolveToken layer)', async () => {
107-
getLinearSecretMock.mockRejectedValueOnce(new Error('AccessDenied'));
116+
test('returns false when resolveLinearOauthToken throws (swallowed at resolveToken layer)', async () => {
117+
resolveLinearOauthTokenMock.mockRejectedValueOnce(new Error('AccessDenied'));
108118

109-
const ok = await postIssueComment(SECRET_ARN, ISSUE_ID, 'msg');
119+
const ok = await postIssueComment(CTX, ISSUE_ID, 'msg');
110120

111121
expect(ok).toBe(false);
112122
expect(fetchMock).not.toHaveBeenCalled();
@@ -115,7 +125,7 @@ describe('linear-feedback', () => {
115125

116126
describe('addIssueReaction', () => {
117127
test('defaults to ❌ (emoji short-code "x")', async () => {
118-
await addIssueReaction(SECRET_ARN, ISSUE_ID);
128+
await addIssueReaction(CTX, ISSUE_ID);
119129

120130
const init = fetchMock.mock.calls[0][1];
121131
const body = JSON.parse(init.body as string) as { query: string; variables: { emoji: string } };
@@ -124,7 +134,7 @@ describe('linear-feedback', () => {
124134
});
125135

126136
test('honours an explicit emoji argument', async () => {
127-
await addIssueReaction(SECRET_ARN, ISSUE_ID, 'eyes');
137+
await addIssueReaction(CTX, ISSUE_ID, 'eyes');
128138

129139
const init = fetchMock.mock.calls[0][1];
130140
const body = JSON.parse(init.body as string) as { variables: { emoji: string } };
@@ -134,7 +144,7 @@ describe('linear-feedback', () => {
134144

135145
describe('reportIssueFailure', () => {
136146
test('posts comment + ❌ in parallel via Promise.allSettled', async () => {
137-
await reportIssueFailure(SECRET_ARN, ISSUE_ID, '❌ failed');
147+
await reportIssueFailure(CTX, ISSUE_ID, '❌ failed');
138148

139149
expect(fetchMock).toHaveBeenCalledTimes(2);
140150
const queries = fetchMock.mock.calls.map((c) => {
@@ -151,13 +161,13 @@ describe('linear-feedback', () => {
151161
.mockResolvedValueOnce(jsonResponse({}, 500))
152162
.mockResolvedValueOnce(jsonResponse({ data: { reactionCreate: { success: true } } }));
153163

154-
await expect(reportIssueFailure(SECRET_ARN, ISSUE_ID, 'msg')).resolves.toBeUndefined();
164+
await expect(reportIssueFailure(CTX, ISSUE_ID, 'msg')).resolves.toBeUndefined();
155165
});
156166

157167
test('does not throw when both legs fail', async () => {
158168
fetchMock.mockRejectedValue(new Error('ECONNRESET'));
159169

160-
await expect(reportIssueFailure(SECRET_ARN, ISSUE_ID, 'msg')).resolves.toBeUndefined();
170+
await expect(reportIssueFailure(CTX, ISSUE_ID, 'msg')).resolves.toBeUndefined();
161171
});
162172
});
163173
});

0 commit comments

Comments
 (0)