import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import test from 'node:test' import { fileURLToPath } from 'node:url' import { DEFAULT_GITLAB_API_BASE_URL, DEFAULT_GITLAB_INTEGRATION_NAME, DEFAULT_GITLAB_SECRET_NAME, GITLAB_OAUTH_CONNECT_URL, GITLAB_PAT_SETUP_URL, buildOauthConnectUrl, buildPatSetupUrl, resolveApiBaseUrl, resolveGitlabAuth, } from '../src/auth.ts' import { encodeProjectId, normalizeIssueLocator, normalizeMergeRequestLocator, normalizePipelineLocator, normalizeProjectLocator, } from '../src/locators.ts' const root = join(dirname(fileURLToPath(import.meta.url)), '..') test('default auth is OAuth integration gitlab', () => { const auth = resolveGitlabAuth() assert.equal(auth.mode, 'oauth') assert.equal(auth.integrationName, DEFAULT_GITLAB_INTEGRATION_NAME) assert.equal(auth.apiBaseUrl, DEFAULT_GITLAB_API_BASE_URL) assert.equal(auth.apiHost, 'gitlab.com') }) test('secretName selects the PAT lane and ignores integrationName', () => { const auth = resolveGitlabAuth({ secretName: DEFAULT_GITLAB_SECRET_NAME, integrationName: 'gitlab-work', }) assert.equal(auth.mode, 'pat') assert.equal(auth.secretName, DEFAULT_GITLAB_SECRET_NAME) assert.equal(auth.integrationName, null) assert.equal(auth.label, DEFAULT_GITLAB_SECRET_NAME) }) test('multi-account OAuth uses gitlab-* integration names', () => { const auth = resolveGitlabAuth({ integrationName: 'gitlab-work' }) assert.equal(auth.mode, 'oauth') assert.equal(auth.integrationName, 'gitlab-work') assert.equal(auth.label, 'gitlab-work') }) test('account aliases integrationName', () => { const auth = resolveGitlabAuth({ account: 'gitlab-bot' }) assert.equal(auth.integrationName, 'gitlab-bot') }) test('self-hosted instanceUrl becomes /api/v4', () => { assert.equal( resolveApiBaseUrl({ instanceUrl: 'https://gitlab.example.com' }), 'https://gitlab.example.com/api/v4', ) assert.equal( resolveApiBaseUrl({ apiBaseUrl: 'https://gitlab.example.com/api/v4/' }), 'https://gitlab.example.com/api/v4', ) }) test('rejects non-https API bases', () => { assert.throws(() => resolveApiBaseUrl({ instanceUrl: 'http://gitlab.example.com' }), /https/) }) test('connect URLs are prefilled and generic', () => { assert.match(GITLAB_OAUTH_CONNECT_URL, /^https:\/\/kody\.codes\/connect\/oauth\?/) assert.match(GITLAB_OAUTH_CONNECT_URL, /provider=gitlab/) assert.match(GITLAB_OAUTH_CONNECT_URL, /authorizeUrl=https%3A%2F%2Fgitlab.com%2Foauth%2Fauthorize/) assert.match(GITLAB_OAUTH_CONNECT_URL, /tokenUrl=https%3A%2F%2Fgitlab.com%2Foauth%2Ftoken/) assert.match(GITLAB_OAUTH_CONNECT_URL, /allowedHosts=gitlab.com/) assert.match(GITLAB_PAT_SETUP_URL, /^https:\/\/kody\.codes\/account\/secrets\/new\?/) assert.match(GITLAB_PAT_SETUP_URL, /name=gitlabAccessToken/) assert.match(GITLAB_PAT_SETUP_URL, /allowedHosts=gitlab.com/) const work = buildOauthConnectUrl({ provider: 'gitlab-work' }) assert.match(work, /provider=gitlab-work/) const selfHosted = buildOauthConnectUrl({ provider: 'gitlab', instanceUrl: 'https://gitlab.example.com', }) assert.match(selfHosted, /gitlab.example.com%2Foauth%2Fauthorize/) assert.equal( buildPatSetupUrl('gitlabAccessTokenWork', 'gitlab.example.com').includes( 'allowedHosts=gitlab.example.com', ), true, ) }) test('encodes namespace/project paths once', () => { assert.equal(encodeProjectId(42), '42') assert.equal(encodeProjectId('example-group/example-project'), 'example-group%2Fexample-project') assert.equal(encodeProjectId('example-group%2Fexample-project'), 'example-group%2Fexample-project') }) test('project locators accept id, path, and URL', () => { assert.deepEqual(normalizeProjectLocator({ project: 99 }), { projectId: '99', projectPath: '99', }) assert.deepEqual(normalizeProjectLocator({ project: 'example-group/nested/example-project' }), { projectId: 'example-group%2Fnested%2Fexample-project', projectPath: 'example-group/nested/example-project', }) assert.deepEqual( normalizeProjectLocator({ projectUrl: 'https://gitlab.com/example-group/example-project/-/issues/3', }), { projectId: 'example-group%2Fexample-project', projectPath: 'example-group/example-project', }, ) }) test('issue, merge request, and pipeline locators parse GitLab URLs', () => { assert.deepEqual( normalizeIssueLocator({ issueUrl: 'https://gitlab.com/example-group/nested/example-project/-/issues/12', }), { projectId: 'example-group%2Fnested%2Fexample-project', projectPath: 'example-group/nested/example-project', issueIid: 12, }, ) assert.deepEqual( normalizeMergeRequestLocator({ project: 'example-group/example-project', mrIid: 4, }), { projectId: 'example-group%2Fexample-project', projectPath: 'example-group/example-project', mergeRequestIid: 4, }, ) assert.deepEqual( normalizePipelineLocator({ pipelineUrl: 'https://gitlab.example.com/example-group/example-project/-/pipelines/88', }), { projectId: 'example-group%2Fexample-project', projectPath: 'example-group/example-project', pipelineId: 88, }, ) }) test('rejects GitHub URLs', () => { assert.throws( () => normalizeProjectLocator({ projectUrl: 'https://github.com/example-org/example', }), /not GitHub/, ) }) test('source and docs stay generic', () => { const files = [ 'README.md', 'package.json', 'src/index.ts', 'src/auth.ts', 'src/locators.ts', 'src/issues/create.ts', 'src/merge-requests/create.ts', ] for (const file of files) { const text = readFileSync(join(root, file), 'utf8') assert.doesNotMatch(text, /kentcdodds/i) assert.doesNotMatch(text, /kent-explicit/) assert.doesNotMatch(text, /epicweb/i) assert.doesNotMatch(text, /\/community\/[0-9a-f-]{36}/) } const readme = readFileSync(join(root, 'README.md'), 'utf8') assert.match(readme, /^## Intent$/m) assert.match(readme, /https:\/\/kody\.codes\/@kody\/gitlab/) assert.match(readme, /https:\/\/kody\.codes\/connect\/oauth\?provider=gitlab/) assert.match(readme, /https:\/\/kody\.codes\/account\/secrets\/new\?name=gitlabAccessToken/) })