Skip to content

Commit 7c21617

Browse files
authored
fix: remove configured field check from Auth class (#308)
After PR #306 removed the 'configured' field from the Repository schema, the Auth class was still checking for it. This caused all repositories to fail authentication with 'No authentication methods available' errors. Changes: - Remove obsolete configured field check from Auth.getAllMethods() - Fix auth.test.js to test real Auth class instead of test double - Update mock data to match actual schema (no configured field) - Add regression test for this specific bug scenario Why the test didn't catch it: The auth.test.js file used a TestAuth class that duplicated the implementation with the SAME bug (checking configured field). Tests passed because they tested the test code, not the real code. This fix ensures: - Authentication works with repositories that have installationId - Tests now use the actual Auth class - Mock data matches real database schema - Future schema changes will be caught by tests
1 parent 2e2247c commit 7c21617

File tree

2 files changed

+77
-88
lines changed

2 files changed

+77
-88
lines changed

src/helpers/auth.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class Auth {
5454
this.repo
5555
);
5656

57-
if (repository && repository.configured && repository.installationId) {
57+
if (repository && repository.installationId) {
5858
this._methods.push({
5959
type: 'APP',
6060
installationId: repository.installationId,

tests/auth.test.js

Lines changed: 76 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2,87 +2,27 @@ import { test } from 'node:test';
22
import assert from 'node:assert';
33
import sinon from 'sinon';
44
import './setup.js';
5-
6-
// Mock the models by setting up stubs directly
7-
const mockUser = {
8-
findById: sinon.stub(),
9-
};
10-
11-
const mockRepository = {
12-
findByOwnerAndRepo: sinon.stub(),
13-
};
14-
15-
// We'll override the imports in the Auth module by mocking the module loading
16-
const originalImport = await import('../src/helpers/auth.js');
17-
18-
// Create a test-specific Auth class that uses our mocks
19-
class TestAuth extends originalImport.Auth {
20-
constructor(options) {
21-
super(options);
22-
// Override the models used by the Auth class
23-
this._testRepository = mockRepository;
24-
}
25-
26-
async getAllMethods() {
27-
// Copy the original method but use our mocked models
28-
if (this._methods) return this._methods;
29-
30-
this._methods = [];
31-
32-
// Priority 1: Repository GitHub App authentication
33-
try {
34-
const repository = await this._testRepository.findByOwnerAndRepo(
35-
this.owner,
36-
this.repo
37-
);
38-
39-
if (repository && repository.configured && repository.installationId) {
40-
this._methods.push({
41-
type: 'APP',
42-
installationId: repository.installationId,
43-
priority: 1,
44-
description: 'Repository GitHub App',
45-
});
46-
}
47-
} catch (error) {
48-
console.warn('Failed to load repository config:', error.message);
49-
}
50-
51-
// Priority 2: Environment token fallback (if available)
52-
if (process.env.GITHUB_FALLBACK_TOKEN) {
53-
this._methods.push({
54-
type: 'ENV',
55-
token: process.env.GITHUB_FALLBACK_TOKEN,
56-
priority: 2,
57-
description: 'Environment fallback token',
58-
});
59-
}
60-
61-
// Sort by priority and return
62-
this._methods.sort((a, b) => a.priority - b.priority);
63-
return this._methods;
64-
}
65-
}
5+
import { Repository } from '../src/database/models.js';
6+
import { Auth } from '../src/helpers/auth.js';
667

678
test('Auth class', async t => {
9+
let findByOwnerAndRepoStub;
10+
6811
t.beforeEach(() => {
69-
// Reset all stubs before each test
70-
mockUser.findById.reset();
71-
mockRepository.findByOwnerAndRepo.reset();
12+
// Mock Repository.findByOwnerAndRepo at module level
13+
findByOwnerAndRepoStub = sinon.stub(Repository, 'findByOwnerAndRepo');
7214

7315
// Clear environment variable
7416
delete process.env.GITHUB_FALLBACK_TOKEN;
7517
});
7618

77-
t.after(() => {
78-
// Clear any remaining timers or handles
79-
if (global.gc) {
80-
global.gc();
81-
}
19+
t.afterEach(() => {
20+
// Restore original implementation
21+
findByOwnerAndRepoStub.restore();
8222
});
8323

8424
await t.test('should create auth with minimal parameters', async () => {
85-
const auth = new TestAuth({ owner: 'test', repo: 'repo' });
25+
const auth = new Auth({ owner: 'test', repo: 'repo' });
8626
assert.ok(auth);
8727
assert.strictEqual(auth.owner, 'test');
8828
assert.strictEqual(auth.repo, 'repo');
@@ -91,9 +31,9 @@ test('Auth class', async t => {
9131
await t.test(
9232
'should return empty methods when no auth available',
9333
async () => {
94-
mockRepository.findByOwnerAndRepo.resolves(null);
34+
findByOwnerAndRepoStub.resolves(null);
9535

96-
const auth = new TestAuth({ owner: 'test', repo: 'repo' });
36+
const auth = new Auth({ owner: 'test', repo: 'repo' });
9737
const methods = await auth.getAllMethods();
9838

9939
assert.strictEqual(methods.length, 0);
@@ -102,13 +42,15 @@ test('Auth class', async t => {
10242
);
10343

10444
await t.test('should prioritize GitHub App when available', async () => {
45+
// Mock repository WITHOUT 'configured' field (matches actual schema after PR #306)
10546
const mockRepo = {
106-
configured: true,
47+
owner: 'test',
48+
repo: 'repo',
10749
installationId: 12345,
10850
};
109-
mockRepository.findByOwnerAndRepo.resolves(mockRepo);
51+
findByOwnerAndRepoStub.resolves(mockRepo);
11052

111-
const auth = new TestAuth({ owner: 'test', repo: 'repo' });
53+
const auth = new Auth({ owner: 'test', repo: 'repo' });
11254
const methods = await auth.getAllMethods();
11355

11456
assert.strictEqual(methods.length, 1);
@@ -119,14 +61,16 @@ test('Auth class', async t => {
11961
});
12062

12163
await t.test('should use both GitHub App and fallback token', async () => {
64+
// Mock repository WITHOUT 'configured' field
12265
const mockRepo = {
123-
configured: true,
66+
owner: 'test',
67+
repo: 'repo',
12468
installationId: 12345,
12569
};
126-
mockRepository.findByOwnerAndRepo.resolves(mockRepo);
70+
findByOwnerAndRepoStub.resolves(mockRepo);
12771
process.env.GITHUB_FALLBACK_TOKEN = 'env-token';
12872

129-
const auth = new TestAuth({ owner: 'test', repo: 'repo' });
73+
const auth = new Auth({ owner: 'test', repo: 'repo' });
13074
const methods = await auth.getAllMethods();
13175

13276
assert.strictEqual(methods.length, 2);
@@ -138,9 +82,9 @@ test('Auth class', async t => {
13882

13983
await t.test('should add environment token when available', async () => {
14084
process.env.GITHUB_FALLBACK_TOKEN = 'env-token';
141-
mockRepository.findByOwnerAndRepo.resolves(null);
85+
findByOwnerAndRepoStub.resolves(null);
14286

143-
const auth = new TestAuth({ owner: 'test', repo: 'repo' });
87+
const auth = new Auth({ owner: 'test', repo: 'repo' });
14488
const methods = await auth.getAllMethods();
14589

14690
assert.strictEqual(methods.length, 1);
@@ -151,14 +95,16 @@ test('Auth class', async t => {
15195
});
15296

15397
await t.test('should provide auth strategy description', async () => {
98+
// Mock repository WITHOUT 'configured' field
15499
const mockRepo = {
155-
configured: true,
100+
owner: 'test',
101+
repo: 'repo',
156102
installationId: 12345,
157103
};
158-
mockRepository.findByOwnerAndRepo.resolves(mockRepo);
104+
findByOwnerAndRepoStub.resolves(mockRepo);
159105
process.env.GITHUB_FALLBACK_TOKEN = 'env-token';
160106

161-
const auth = new TestAuth({ owner: 'test', repo: 'repo' });
107+
const auth = new Auth({ owner: 'test', repo: 'repo' });
162108
const strategy = await auth.getAuthStrategy();
163109

164110
assert.ok(strategy.includes('Auth strategy (with fallbacks)'));
@@ -167,20 +113,63 @@ test('Auth class', async t => {
167113
});
168114

169115
await t.test('should cache methods on repeated calls', async () => {
116+
// Mock repository WITHOUT 'configured' field
170117
const mockRepo = {
171-
configured: true,
118+
owner: 'test',
119+
repo: 'repo',
172120
installationId: 12345,
173121
};
174-
mockRepository.findByOwnerAndRepo.resolves(mockRepo);
122+
findByOwnerAndRepoStub.resolves(mockRepo);
175123

176-
const auth = new TestAuth({ owner: 'test', repo: 'repo' });
124+
const auth = new Auth({ owner: 'test', repo: 'repo' });
177125

178126
// First call
179127
const methods1 = await auth.getAllMethods();
180128
// Second call should use cache
181129
const methods2 = await auth.getAllMethods();
182130

183131
assert.strictEqual(methods1, methods2); // Same reference
184-
assert.strictEqual(mockRepository.findByOwnerAndRepo.callCount, 1); // Only called once
132+
assert.strictEqual(findByOwnerAndRepoStub.callCount, 1); // Only called once
185133
});
134+
135+
await t.test('should handle repository without installationId', async () => {
136+
// Repository exists but has no installationId
137+
const mockRepo = {
138+
owner: 'test',
139+
repo: 'repo',
140+
// No installationId
141+
};
142+
findByOwnerAndRepoStub.resolves(mockRepo);
143+
144+
const auth = new Auth({ owner: 'test', repo: 'repo' });
145+
const methods = await auth.getAllMethods();
146+
147+
// Should have no GitHub App method
148+
assert.strictEqual(methods.length, 0);
149+
assert.strictEqual(await auth.hasValidAuth(), false);
150+
});
151+
152+
await t.test(
153+
'REGRESSION: should work with installationId but no configured field',
154+
async () => {
155+
// This is the exact scenario that caused the bug:
156+
// After PR #306, 'configured' field was removed from schema
157+
// Auth class was still checking for it, causing all repos to fail
158+
const mockRepo = {
159+
owner: 'test',
160+
repo: 'repo',
161+
installationId: 12345,
162+
// NOTE: No 'configured' field - this matches the actual schema
163+
};
164+
findByOwnerAndRepoStub.resolves(mockRepo);
165+
166+
const auth = new Auth({ owner: 'test', repo: 'repo' });
167+
const methods = await auth.getAllMethods();
168+
169+
// Should work correctly - the fix removed the configured check
170+
assert.strictEqual(methods.length, 1);
171+
assert.strictEqual(methods[0].type, 'APP');
172+
assert.strictEqual(methods[0].installationId, 12345);
173+
}
174+
);
186175
});

0 commit comments

Comments
 (0)