Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions backend/tests/auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,36 @@ test('POST /api/auth/storage/create returns probe failures without needsInstall'
assert.match(res.body.error, /do not reinstall/i);
});

test('POST /api/auth/storage/create returns REPO_CREATE_FORBIDDEN on Administration 403', async () => {
const app = createAuthedApp({
user: AUTHED_USER,
authService: {
clientsFor: async () => ({ githubUserClient: { mock: true } }),
getInstallationSetupUrl: async () =>
'https://github.com/apps/vizably/installations/new',
},
storageService: {
createGitHubRepository: async () => {
const err = new Error(
'GitHub App cannot create repositories. Add Repository permissions → Administration: Read and write, accept the permission upgrade on your installation, then sign out and sign in again.',
);
err.status = 403;
err.code = 'REPO_CREATE_FORBIDDEN';
throw err;
},
},
});
const res = await request(app)
.post('/api/auth/storage/create')
.send({ name: 'vizably-new' });
assert.equal(res.status, 403);
assert.equal(res.body.code, 'REPO_CREATE_FORBIDDEN');
assert.equal(res.body.needsInstall, undefined);
assert.equal(res.body.storageRef, undefined);
assert.match(res.body.error, /Administration:\s*Read and write/i);
assert.match(res.body.error, /permission upgrade/i);
});

test('POST /api/auth/storage/validate requires provider and storageRef', async () => {
const app = createAuthedApp({
user: AUTHED_USER,
Expand Down
40 changes: 40 additions & 0 deletions backend/tests/storageService.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,46 @@ test('createGitHubRepository maps name-taken conflicts', async () => {
);
});

test('createGitHubRepository maps Administration permission 403 to REPO_CREATE_FORBIDDEN', async () => {
const storageService = new StorageService();
const forbidden = new Error('Resource not accessible by integration');
forbidden.status = 403;
forbidden.response = {
data: { message: 'Resource not accessible by integration' },
};
const client = createMockGitHubClient({ createRepoError: forbidden });
await assert.rejects(
() => storageService.createGitHubRepository('vizably-new', { githubUserClient: client }),
(err) => {
assert.equal(err.code, 'REPO_CREATE_FORBIDDEN');
assert.equal(err.status, 403);
assert.match(err.message, /Administration:\s*Read and write/i);
assert.match(err.message, /permission upgrade/i);
return true;
},
);
});

test('createGitHubRepository maps generic create 403 to REPO_CREATE_FORBIDDEN', async () => {
const storageService = new StorageService();
const forbidden = new Error('Forbidden');
forbidden.status = 403;
forbidden.response = {
data: { message: 'Although you appear to have the correct authorization credentials, organization policy prevents creating repositories.' },
};
const client = createMockGitHubClient({ createRepoError: forbidden });
await assert.rejects(
() => storageService.createGitHubRepository('vizably-new', { githubUserClient: client }),
(err) => {
assert.equal(err.code, 'REPO_CREATE_FORBIDDEN');
assert.equal(err.status, 403);
assert.match(err.message, /organization policy prevents creating repositories/i);
assert.doesNotMatch(err.message, /Administration/);
return true;
},
);
});

test('validateStorage returns initializable for empty repo', async () => {
const storageService = new StorageService();
const client = createMockGitHubClient();
Expand Down