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
106 changes: 57 additions & 49 deletions test/content/da-auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@ import { makeLogger } from './content-test-utils.js';

const TEST_PROJECT_DIR = '/tmp/test-da-project';

function waitForTimeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

describe('getValidToken', () => {
it('returns override token immediately without reading stored token', async () => {
let fseCalled = false;
Expand Down Expand Up @@ -60,6 +54,36 @@ describe('getValidToken', () => {
assert.strictEqual(result, 'stored-token');
});

// Mock http so no real socket is bound and no real 5-minute timeout timer is
// started (the real waitForToken() callback server would otherwise leak an
// open handle/timer past the end of the test, delaying the whole suite's exit).
// When listen() is called, simulate the /token request firing immediately.
// Also stub ensureGitIgnored, since it's covered by its own dedicated test below
// and would otherwise try to write a real .gitignore under TEST_PROJECT_DIR.
const mockHttp = {
'node:http': {
default: {
createServer: (reqHandler) => {
const mockServer = {
listen: () => {
setImmediate(() => {
const fakeReq = { url: '/token?access_token=test-token&expires_in=3600' };
const fakeRes = { writeHead: () => {}, end: () => {} };
reqHandler(fakeReq, fakeRes);
});
},
close: () => {},
on: () => {},
};
return mockServer;
},
},
},
'../../src/content/content-git.js': {
ensureGitIgnored: async () => {},
},
};

it('logs expiry message and calls login when stored token is expired', async () => {
const tokenData = {
access_token: 'old-token',
Expand All @@ -74,22 +98,13 @@ describe('getValidToken', () => {
writeJson: async () => {},
},
open: async () => {},
...mockHttp,
});
const log = makeLogger();
try {
await Promise.race([
getValidToken(log, undefined, TEST_PROJECT_DIR),
waitForTimeout(100).then(() => { throw new Error('timeout'); }),
]);
} catch (err) {
const msgs = log.logs.map((l) => l.msg).join(' ');
const isExpectedError = err.message === 'timeout'
|| err.message.includes('login')
|| err.message.includes('EADDRINUSE')
|| err.message.includes('callback');
const hasExpiredLog = msgs.includes('expired') || msgs.includes('login') || msgs.includes('browser');
assert.ok(isExpectedError || hasExpiredLog);
}
const result = await getValidToken(log, undefined, TEST_PROJECT_DIR);
assert.strictEqual(result, 'test-token');
const msgs = log.logs.map((l) => l.msg).join(' ');
assert.ok(msgs.includes('expired'));
});

it('proceeds to login when no stored token file exists', async () => {
Expand All @@ -101,21 +116,11 @@ describe('getValidToken', () => {
writeJson: async () => {},
},
open: async () => {},
...mockHttp,
});
const log = makeLogger();
try {
await Promise.race([
getValidToken(log, undefined, TEST_PROJECT_DIR),
waitForTimeout(100).then(() => { throw new Error('timeout'); }),
]);
} catch (err) {
assert.ok(
err.message === 'timeout'
|| err.message.includes('login')
|| err.message.includes('EADDRINUSE')
|| err.message.includes('callback'),
);
}
const result = await getValidToken(log, undefined, TEST_PROJECT_DIR);
assert.strictEqual(result, 'test-token');
});

it('treats stored token without expires_at as expired', async () => {
Expand All @@ -129,21 +134,13 @@ describe('getValidToken', () => {
writeJson: async () => {},
},
open: async () => {},
...mockHttp,
});
const log = makeLogger();
try {
await Promise.race([
getValidToken(log, undefined, TEST_PROJECT_DIR),
waitForTimeout(100).then(() => { throw new Error('timeout'); }),
]);
} catch (err) {
assert.ok(
err.message === 'timeout'
|| err.message.includes('login')
|| err.message.includes('EADDRINUSE')
|| err.message.includes('callback'),
);
}
const result = await getValidToken(log, undefined, TEST_PROJECT_DIR);
assert.strictEqual(result, 'test-token');
const msgs = log.logs.map((l) => l.msg).join(' ');
assert.ok(msgs.includes('expired'));
});

it('calls ensureGitIgnored with the token file path after login', async () => {
Expand Down Expand Up @@ -211,12 +208,23 @@ describe('getValidToken', () => {

describe('startDaLoginRedirect', () => {
it('returns the fixed :9898 callback as redirect_uri, regardless of the return url\'s own port', async () => {
// Mock http so no real socket is bound (the fire-and-forget callback server
// this starts is never asked for a token in this test).
// Mock http so no real socket is bound, and simulate an immediate /token
// callback so the internal 5-minute timeout timer gets cleared right away
// instead of leaking a handle for 5 minutes past the end of the test.
const { startDaLoginRedirect } = await esmock('../../src/content/da-auth.js', {
'node:http': {
default: {
createServer: () => ({ listen: () => {}, close: () => {}, on: () => {} }),
createServer: (reqHandler) => ({
listen: () => {
setImmediate(() => {
const fakeReq = { url: '/token?access_token=test-token&expires_in=3600' };
const fakeRes = { writeHead: () => {}, end: () => {} };
reqHandler(fakeReq, fakeRes);
});
},
close: () => {},
on: () => {},
}),
},
},
});
Expand Down
9 changes: 8 additions & 1 deletion test/import-cmd.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,18 @@ import {
} from './utils.js';
import ImportCommand from '../src/import.cmd.js';

const { fetch } = fetchContext({ rejectUnauthorized: false });
const importFetchCtx = fetchContext({ rejectUnauthorized: false });
const { fetch } = importFetchCtx;

const TEST_DIR = path.resolve(__rootdir, 'test', 'fixtures', 'import');
const SAMPLE_HOST = 'http://www.sample.com';

after(async () => {
// close the keep-alive sockets of the module-level fetch context so the
// test process doesn't hang after the suite finishes.
await importFetchCtx.reset();
});

describe('Integration test for import command', function suite() {
let nock;

Expand Down
6 changes: 5 additions & 1 deletion test/server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,14 +284,17 @@ describe('Helix Server', () => {
// create a http proxy server
process.env.ALL_PROXY = 'http://127.0.0.1:8002';
const proxyRequests = [];
// create a single fetch context for the proxy so it can be reset (i.e. its
// sockets closed) once the test is done, instead of leaking one per request.
const proxyFetchCtx = h1NoCache();
const proxy = await new Promise((resolve) => {
const p = http
.createServer(async (req, res) => {
try {
// Delete accept header due to nock conflict
delete req.headers.accept;
console.log('http proxy request', req.url);
const resp = await h1NoCache().fetch(req.url, {});
const resp = await proxyFetchCtx.fetch(req.url, {});
console.log('http proxy response for', req.url, resp.status);
res.writeHead(resp.status, resp.headers.plain());
res.write(await resp.buffer());
Expand Down Expand Up @@ -336,6 +339,7 @@ describe('Helix Server', () => {
assert.deepStrictEqual(proxyRequests, [`http://127.0.0.1:${project.server.port}/readme.html`]);
} finally {
proxy.close();
await proxyFetchCtx.reset();
await project.stop();
delete process.env.ALL_PROXY;
}
Expand Down
10 changes: 10 additions & 0 deletions test/setup-env.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
*/
import { resolve } from 'path';
import { fileURLToPath } from 'url';
import { resetContext } from '../src/fetch-utils.js';

// eslint-disable-next-line no-underscore-dangle
global.__rootdir = resolve(fileURLToPath(import.meta.url), '..', '..');

// eslint-disable-next-line import/prefer-default-export
export const mochaHooks = {
async afterAll() {
// ensure keep-alive fetch contexts (and their sockets) don't keep the
// process alive after the test suite has finished.
await resetContext();
},
};
Loading