mirror of
				https://github.com/public-transport/db-vendo-client.git
				synced 2025-11-04 10:06:32 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			144 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			144 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
// Polly's HTTP adapter uses nock [1] underneath, with currently monkey-patches the built-in `node:http` module. For this to work, it must be imported quite early, before many other parts of hafas-client.
 | 
						|
// Importing the adapter itself has no side effects (or rather: immediately undoes nock's monkey-patching, to re-patch when it is actually getting used). We activate it (by passing it into Polly's core) below.
 | 
						|
// remotely related: https://github.com/nock/nock/issues/2461
 | 
						|
import NodeHttpAdapter from '@pollyjs/adapter-node-http';
 | 
						|
 | 
						|
import isRoughlyEqual from 'is-roughly-equal';
 | 
						|
import {ok, AssertionError} from 'assert';
 | 
						|
import {DateTime} from 'luxon';
 | 
						|
import * as a from 'assert';
 | 
						|
import {createRequire} from 'module';
 | 
						|
// Note: We *must* import tap up here, not just in the if(VCR_MODE) block below, because a) we can only use `require()` conditionally, not `import`; and b) requiring it below makes the teardown script behave differently.
 | 
						|
// may be related: https://github.com/tapjs/tapjs/issues/1005
 | 
						|
import tap from 'tap';
 | 
						|
import {gunzipSync} from 'zlib';
 | 
						|
 | 
						|
const hour = 60 * 60 * 1000;
 | 
						|
const day = 24 * hour;
 | 
						|
const week = 7 * day;
 | 
						|
 | 
						|
// next Monday 10 am
 | 
						|
const createWhen = (timezone, locale, tMock) => {
 | 
						|
	ok(Number.isInteger(tMock), 'tMock must be an integer');
 | 
						|
 | 
						|
	const t = process.env.VCR_MODE && !process.env.VCR_OFF
 | 
						|
		? tMock
 | 
						|
		: Date.now();
 | 
						|
	return DateTime.fromMillis(t, {
 | 
						|
		zone: timezone,
 | 
						|
		locale,
 | 
						|
	})
 | 
						|
		.startOf('week')
 | 
						|
		.plus({weeks: 1, hours: 10})
 | 
						|
		.toJSDate();
 | 
						|
};
 | 
						|
 | 
						|
const assertValidWhen = (actual, expected, name, delta = day + 6 * hour) => {
 | 
						|
	const ts = Number(new Date(actual));
 | 
						|
	a.ok(!Number.isNaN(ts), name + ' is not parsable by Date');
 | 
						|
	// the timestamps might be from long-distance trains
 | 
						|
	if (!isRoughlyEqual(delta, Number(expected), ts)) {
 | 
						|
		throw new AssertionError({
 | 
						|
			message: name + ' is out of range',
 | 
						|
			actual: ts,
 | 
						|
			expected: `${expected - delta} - ${Number(expected) + delta}`,
 | 
						|
			operator: 'isRoughlyEqual',
 | 
						|
		});
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
// HTTP request mocking
 | 
						|
if (process.env.VCR_MODE && !process.env.VCR_OFF) {
 | 
						|
	const require = createRequire(import.meta.url);
 | 
						|
 | 
						|
	const {Polly} = require('@pollyjs/core');
 | 
						|
	const NodeHttpAdapter = require('@pollyjs/adapter-node-http');
 | 
						|
	const FSPersister = require('@pollyjs/persister-fs');
 | 
						|
 | 
						|
	// monkey-patch NodeHttpAdapter to handle gzipped responses properly
 | 
						|
	// todo: submit a PR
 | 
						|
	// related: https://github.com/Netflix/pollyjs/issues/256
 | 
						|
	// related: https://github.com/Netflix/pollyjs/issues/463
 | 
						|
	// related: https://github.com/Netflix/pollyjs/issues/207
 | 
						|
	const _getBodyFromChunks = NodeHttpAdapter.prototype.getBodyFromChunks;
 | 
						|
	NodeHttpAdapter.prototype.getBodyFromChunks = function getBodyFromChunksWithGunzip (chunks, headers) {
 | 
						|
		if (headers['content-encoding'] === 'gzip') {
 | 
						|
			const concatenated = Buffer.concat(chunks);
 | 
						|
			chunks = [gunzipSync(concatenated)];
 | 
						|
			// todo: this is ugly, find a better way
 | 
						|
			delete headers['content-encoding'];
 | 
						|
			headers['content-length'] = chunks[0].length;
 | 
						|
		}
 | 
						|
		return _getBodyFromChunks.call(this, chunks, headers);
 | 
						|
	};
 | 
						|
 | 
						|
	Polly.register(NodeHttpAdapter);
 | 
						|
	Polly.register(FSPersister);
 | 
						|
 | 
						|
	// https://github.com/Netflix/pollyjs/blob/9b6bede12b7ee998472b8883c9dd01e2159e00a8/packages/%40pollyjs/adapter/src/index.js#L184-L189
 | 
						|
	if ('navigator' in global && global.navigator && !('onLine' in global.navigator)) {
 | 
						|
		global.navigator.onLine = true;
 | 
						|
	}
 | 
						|
 | 
						|
	let mode;
 | 
						|
	if (process.env.VCR_MODE === 'record') {
 | 
						|
		mode = 'record';
 | 
						|
	} else if (process.env.VCR_MODE === 'playback') {
 | 
						|
		mode = 'replay';
 | 
						|
	} else {
 | 
						|
		throw new Error('invalid $VCR_MODE, must be "record" or "replay"');
 | 
						|
	}
 | 
						|
 | 
						|
	const polly = new Polly('requests', {
 | 
						|
		logLevel: 'warn',
 | 
						|
		// If a request's recording is not found, pass-through to the server and record the response.
 | 
						|
		recordIfMissing: false,
 | 
						|
		// If false, Polly will throw when attempting to persist any failed requests. A request is considered to be a failed request when its response's status code is ≥ 400.
 | 
						|
		recordFailedRequests: true,
 | 
						|
		// Await any unresolved requests handled by the polly instance (via flush) when stop is called.
 | 
						|
		flushRequestsOnStop: true,
 | 
						|
		// The Polly mode. Can be one of the following:
 | 
						|
		// - replay: Replay responses from recordings.
 | 
						|
		// - record: Force Polly to record all requests. This will overwrite recordings that already exist.
 | 
						|
		// - passthrough: Passes all requests through directly to the server without recording or replaying.
 | 
						|
		mode,
 | 
						|
		adapters: ['node-http'],
 | 
						|
		persister: 'fs',
 | 
						|
		persisterOptions: {
 | 
						|
			fs: {
 | 
						|
				recordingsDir: new URL('../fixtures', import.meta.url).pathname,
 | 
						|
			},
 | 
						|
			// When disabled, requests that have not been captured by the running Polly instance will be removed from any previous recording. This ensures that a recording will only contain the requests that were made during the lifespan of the Polly instance. When enabled, new requests will be appended to the recording file.
 | 
						|
			keepUnusedRequests: true, // todo: change to false?
 | 
						|
		},
 | 
						|
		matchRequestsBy: {
 | 
						|
			order: false, // todo: set to true for better Git diffs?
 | 
						|
			headers: {
 | 
						|
				// todo: use an allow-list here?
 | 
						|
				exclude: [
 | 
						|
					// request
 | 
						|
					'user-agent', // randomised
 | 
						|
					'connection', // not relevant for tests
 | 
						|
					// response
 | 
						|
					'set-cookie', // often randomised
 | 
						|
					'date', // constantly changing
 | 
						|
				],
 | 
						|
			},
 | 
						|
		},
 | 
						|
	});
 | 
						|
	// using exclude above would still require re-recording everything...
 | 
						|
	polly.server.any()
 | 
						|
		.on('request', req => {
 | 
						|
			if (req.hasHeader('X-Correlation-ID')) {
 | 
						|
				req.setHeader('X-Correlation-ID', ['null']);
 | 
						|
			}
 | 
						|
		});
 | 
						|
 | 
						|
	tap.teardown(async () => {
 | 
						|
		await polly.stop();
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
export {
 | 
						|
	hour, createWhen, assertValidWhen,
 | 
						|
};
 |