/** * @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com> * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>. * * Any use of the work other than as authorized under this license or copyright law is prohibited. */ import * as td from 'testdouble' import { register } from 'prom-client' import RedisMock from 'ioredis-mock' import zlib from 'node:zlib' import yaml from 'js-yaml' import fastify from 'fastify' import autoLoad from '@fastify/autoload' import sensible from '@fastify/sensible' import urlData from '@fastify/url-data' import formbody from '@fastify/formbody' import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) export function generateSimpleViteManifest (mapping) { const viteManifest = {} for (const [file, value] of Object.entries(mapping)) { viteManifest[file] = { file, meta: typeof value === 'string' ? { manifests: [{ namespace: value }] } : {} } if (typeof value === 'object') Object.assign(viteManifest[file], value) } return viteManifest } export function mockConfig (obj = {}) { return td.replaceEsm('fs/promises', {}, { readFile () { return yaml.dump(obj) } }) } export function mockFetch (servers = {}) { td.replace(global, 'fetch', async function ({ origin, pathname }, ...args) { const response = servers[origin]?.[pathname] if (response === undefined) return new Response('', { status: 404 }) if (response instanceof Function) return response.apply(this, arguments) if (typeof response === 'object') { return new Response(JSON.stringify(response), { status: 200, headers: { 'Content-Type': 'application/json' } }) } return new Response(response, { status: 200 }) }) } export async function mockRedis (data = {}, isEnabled = true) { const mock = { isReady () { return Promise.resolve() }, isEnabled () { return isEnabled }, createClient () { return new RedisMock() }, client: new RedisMock(data) } await td.replaceEsm('../src/redis.js', mock) return mock } export async function injectApp (appRoot = '/') { register.clear() process.env.APP_ROOT = appRoot const { configMap } = await import('../src/config_map.js') const { getLatestVersion } = await import('../src/version.js') await configMap.load() await getLatestVersion() const app = fastify({ disableRequestLogging: true }) app.register(sensible) app.register(urlData) app.register(formbody) const autoLoadOptions = { dir: join(__dirname, '../src/routes'), autoHooks: true } if (appRoot) autoLoadOptions.options = { prefix: String(appRoot).replace(/\/$/, '') } app.register(autoLoad, autoLoadOptions) return app } export async function brotliParser (res, cb) { const brotli = zlib.createBrotliDecompress() let buffer = Buffer.from('') res.pipe(brotli) brotli.on('data', chunk => (buffer = Buffer.concat([buffer, chunk]))) brotli.on('end', () => { let result = buffer.toString() if (res.headers['content-type'].startsWith('application/json')) { result = JSON.parse(result) } cb(null, result) }) } export async function decompressBrotli (encodedData) { const compressedData = Buffer.from(encodedData) return new Promise((resolve, reject) => { zlib.brotliDecompress(compressedData, (err, data) => { if (err) reject(err) else resolve(JSON.parse(data.toString('utf8'))) }) }) } export async function wait (timeout) { return new Promise(resolve => setTimeout(resolve, timeout)) }