// import fs from 'fs-extra' // see fs-extra/esm and fs-extra doc import fs from 'fs' import path from 'path' import dns from 'dns' import https from 'https' import { createRequire } from 'module' import { rimrafSync } from 'rimraf' import fse from 'fs-extra' import Provider from '@solid/oidc-op' import supertest from 'supertest' import ldnode from '../index.mjs' import { fileURLToPath } from 'url' const require = createRequire(import.meta.url) const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const OIDCProvider = Provider const TEST_HOSTS = ['nic.localhost', 'tim.localhost', 'nicola.localhost'] // Configurable test root directory // For custom route let TEST_ROOT = path.join(__dirname, '/resources/') // For default root (process.cwd()): // let TEST_ROOT = path.join(process.cwd(), 'test-esm/resources') export function setTestRoot (rootPath) { TEST_ROOT = rootPath } export function getTestRoot () { return TEST_ROOT } export function rm (file) { return rimrafSync(path.join(TEST_ROOT, file)) } export function cleanDir (dirPath) { fse.removeSync(path.join(dirPath, '.well-known/.acl')) fse.removeSync(path.join(dirPath, '.acl')) fse.removeSync(path.join(dirPath, 'favicon.ico')) fse.removeSync(path.join(dirPath, 'favicon.ico.acl')) fse.removeSync(path.join(dirPath, 'index.html')) fse.removeSync(path.join(dirPath, 'index.html.acl')) fse.removeSync(path.join(dirPath, 'robots.txt')) fse.removeSync(path.join(dirPath, 'robots.txt.acl')) } export function write (text, file) { console.log('Writing to', path.join(TEST_ROOT, file)) // fs.mkdirSync(path.dirname(path.join(TEST_ROOT, file), { recursive: true })) return fs.writeFileSync(path.join(TEST_ROOT, file), text) } export function cp (src, dest) { return fse.copySync( path.join(TEST_ROOT, src), path.join(TEST_ROOT, dest)) } export function read (file) { console.log('Reading from', path.join(TEST_ROOT, file)) return fs.readFileSync(path.join(TEST_ROOT, file), { encoding: 'utf8' }) } // Backs up the given file export function backup (src) { cp(src, src + '.bak') } // Restores a backup of the given file export function restore (src) { cp(src + '.bak', src) rm(src + '.bak') } // Verifies that all HOSTS entries are present export function checkDnsSettings () { return Promise.all(TEST_HOSTS.map(hostname => { return new Promise((resolve, reject) => { dns.lookup(hostname, (error, ip) => { if (error || (ip !== '127.0.0.1' && ip !== '::1')) { reject(error) } else { resolve(true) } }) }) })) .catch(() => { throw new Error(`Expected HOSTS entries of 127.0.0.1 for ${TEST_HOSTS.join()}`) }) } /** * @param configPath {string} * * @returns {Promise} */ export function loadProvider (configPath) { return Promise.resolve() .then(() => { const config = require(configPath) const provider = new OIDCProvider(config) return provider.initializeKeyChain(config.keys) }) } export function createServer (options) { console.log('Creating server with root:', options.root || process.cwd()) return ldnode.createServer(options) } export function setupSupertestServer (options) { const ldpServer = createServer(options) return supertest(ldpServer) } // Lightweight adapter to replace `request` with `node-fetch` in tests // Supports signatures: // - request(options, cb) // - request(url, options, cb) // And methods: get, post, put, patch, head, delete, del function buildAgentFn (options = {}) { const aOpts = options.agentOptions || {} if (!aOpts || (!aOpts.cert && !aOpts.key)) { return undefined } const httpsAgent = new https.Agent({ cert: aOpts.cert, key: aOpts.key, // Tests often run with NODE_TLS_REJECT_UNAUTHORIZED=0; mirror that here rejectUnauthorized: false }) return (parsedURL) => parsedURL.protocol === 'https:' ? httpsAgent : undefined } async function doFetch (method, url, options = {}, cb) { try { const headers = options.headers || {} const body = options.body const agent = buildAgentFn(options) const res = await fetch(url, { method, headers, body, agent }) // Build a response object similar to `request`'s const headersObj = {} res.headers.forEach((value, key) => { headersObj[key] = value }) const response = { statusCode: res.status, statusMessage: res.statusText, headers: headersObj } const hasBody = method !== 'HEAD' const text = hasBody ? await res.text() : '' cb(null, response, text) } catch (err) { cb(err) } } function requestAdapter (arg1, arg2, arg3) { let url, options, cb if (typeof arg1 === 'string') { url = arg1 options = arg2 || {} cb = arg3 } else { options = arg1 || {} url = options.url cb = arg2 } const method = (options && options.method) || 'GET' return doFetch(method, url, options, cb) } ;['GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'DELETE'].forEach(m => { const name = m.toLowerCase() requestAdapter[name] = (options, cb) => doFetch(m, options.url, options, cb) }) // Alias requestAdapter.del = requestAdapter.delete export const httpRequest = requestAdapter // Provide default export for compatibility export default { rm, cleanDir, write, cp, read, backup, restore, checkDnsSettings, loadProvider, createServer, setupSupertestServer, httpRequest }