]> git.ardis.lu - envpass/commitdiff
Refactor global `SERVER` singleton into dependency injection
authorArdis Lu <ardisglu@gmail.com>
Mon, 7 Sep 2026 06:07:08 +0000 (23:07 -0700)
committerArdis Lu <ardisglu@gmail.com>
Mon, 7 Sep 2026 06:07:08 +0000 (23:07 -0700)
src/getPrf.js
test/getPrf.test.js
test/setup.js

index bb2de1913e4745d26c0ccab0184157cc69f43018..cefd1a5bfb571e71046d894cd49cbaf206923e37 100644 (file)
@@ -10,16 +10,10 @@ import open from 'open';
  * Load from disk the HTML to be served by the passkey bridge server.
  * @returns {Promise<string>} The HTML file to serve.
  */
-async function getHTML() {
+export async function getHTMLFromFile() {
   return await readFile(new URL('./getPrf.html', import.meta.url), { encoding: 'utf8' });
 }
 
-/**
- * Wrapper around server operations. This wrapper object is required to mock the methods for
- * testing purposes.
- */
-export const SERVER = { getHTML };
-
 /**
  * 2 minutes in milliseconds. After this duration, the http server will close and the flow
  * must be restarted.
@@ -28,6 +22,8 @@ export const WINDOW_EXPIRATION_DURATION = 2 * 60 * 1000;
 
 /**
  * @typedef {Object} GetPrfOptions
+ * @property {() => string} [getHTML] Function which returns the HTML to serve as the passkey page. The default value
+ * is a function that reads a local `getPrf.html` file from disk and returns it as a string.
  * @property {((url: string) => unknown)|null} [onListening] Function which will be called after the server is ready.
  * The function is passed a URL on `localhost` to complete the passkey flow. The default value is
  * {@link https://github.com/sindresorhus/open|`open`}, which will open the default web browser to the passkey page.
@@ -59,6 +55,7 @@ export const WINDOW_EXPIRATION_DURATION = 2 * 60 * 1000;
  */
 export async function getPrf(options = {}) {
   const {
+    getHTML = getHTMLFromFile,
     onListening = open,
     port = undefined,
     signal = undefined
@@ -72,7 +69,7 @@ export async function getPrf(options = {}) {
   const { promise: prfPromise, resolve: prfResolve } = /** @type {PromiseWithResolvers<Bytes32>}*/(Promise.withResolvers());
   const { promise: abortPromise, resolve: abortResolve } = /** @type {PromiseWithResolvers<null>}*/(Promise.withResolvers());
 
-  const html = await SERVER.getHTML();
+  const html = await getHTML();
 
   // Generate hashes of <style> and <script> contents for the CSP header
   const encoder = new TextEncoder();
index d5be4ccd707fa02d0ef22bca792ae5b429b060a9..cda9b215605d4c01d030a35909cf05714d4b307c 100644 (file)
@@ -4,7 +4,7 @@ import { Readable } from 'node:stream';
 
 import { request } from 'undici';
 
-import { SERVER, WINDOW_EXPIRATION_DURATION } from '#src/getPrf.js';
+import { getHTMLFromFile, WINDOW_EXPIRATION_DURATION } from '#src/getPrf.js';
 import { setupPlaywright, setupServer } from '#test/setup.js';
 
 /**
@@ -56,16 +56,14 @@ function makePostRequest(url, options = {}) {
 
 suite('getPrf.js (server)', () => {
   test('validates <style> tag is present in HTML template', async (t) => {
-    const html = await SERVER.getHTML();
+    const html = await getHTMLFromFile();
     const modified = html.replaceAll('<style>', '');
-    t.mock.method(SERVER, 'getHTML', () => modified);
-    await rejects(setupServer(t), { message: 'Could not find <style> tag in getPrf.html file.' });
+    await rejects(setupServer(t, { getHTML: () => modified }), { message: 'Could not find <style> tag in getPrf.html file.' });
   });
   test('validates <script> tag is present in HTML template', async (t) => {
-    const html = await SERVER.getHTML();
+    const html = await getHTMLFromFile();
     const modified = html.replaceAll('<script type="module">', '');
-    t.mock.method(SERVER, 'getHTML', () => modified);
-    await rejects(setupServer(t), { message: 'Could not find <script> tag in getPrf.html file.' });
+    await rejects(setupServer(t, { getHTML: () => modified }), { message: 'Could not find <script> tag in getPrf.html file.' });
   });
   test('valid GET works', async (t) => {
     const { url } = await setupServer(t);
index c88d088233703856629374027b2cb6bfc9ad6f6c..2d66492c252f71f06703b652a0269eb0daefbbff 100644 (file)
@@ -8,7 +8,7 @@ import { chromium } from 'playwright-core';
 /** @import { Page } from 'playwright-core'; */
 
 import { getPrf } from '#src/getPrf.js';
-/** @import { GetPrfResult } from '#src/getPrf.js'; */
+/** @import { GetPrfOptions, GetPrfResult } from '#src/getPrf.js'; */
 
 /**
  * @typedef {Object} SetupPlaywrightOptions
@@ -62,11 +62,12 @@ export async function setupPlaywright(t, options = {}) {
 /**
  * Setup an HTTP server to orchestrate a passkey flow, with appropriate teardown hook.
  * @param {TestContext} t Test context to add `after` hook to.
+ * @param {GetPrfOptions} [getPrfOptions] Options for the passkey page and server.
  * @returns {Promise<Pick<GetPrfResult,'url'|'prf'>&SetupServerResult>}>}
  */
-export async function setupServer(t) {
+export async function setupServer(t, getPrfOptions = {}) {
   const controller = new AbortController();
-  const { url, prf } = await getPrf({ onListening: null, signal: controller.signal });
+  const { url, prf } = await getPrf({ onListening: null, signal: controller.signal, ...getPrfOptions });
   t.after(() => controller.abort());
   return { controller, url, prf };
 }
This page took 0.046732 seconds and 4 git commands to generate.