@unleash/proxy
Version:
The Unleash Proxy (Open-Source)
466 lines (391 loc) • 13.9 kB
text/typescript
import * as https from 'https';
import * as path from 'path';
import { type ClientFeaturesResponse, Strategy } from 'unleash-client';
import type { StorageProvider } from 'unleash-client/lib/repository/storage-provider';
import { createProxyConfig } from '../config';
test('should require "unleashUrl', () => {
const t = () => createProxyConfig({});
expect(t).toThrow(TypeError);
expect(t).toThrow('You must specify the unleashUrl option (UNLEASH_URL)');
});
test('should require "unleashApiToken', () => {
const t = () => createProxyConfig({ unleashUrl: 'some' });
expect(t).toThrow(TypeError);
expect(t).toThrow(
'You must specify the unleashApiToken option (UNLEASH_API_TOKEN)',
);
});
test('should be valid options', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
proxySecrets: ['s1'],
});
expect(config.unleashUrl).toBe('some');
});
test('should set trust proxy', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
proxySecrets: ['s1'],
trustProxy: true,
});
expect(config.trustProxy).toBe(true);
});
test('should set instanceId', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
unleashInstanceId: 'someId1',
proxySecrets: ['s1'],
});
expect(config.unleashInstanceId).toBe('someId1');
});
test('should generate instanceId', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
proxySecrets: ['s1'],
});
expect(config.unleashInstanceId).toBeDefined();
expect(config.unleashInstanceId.length).toBeGreaterThan(3);
});
test('should set trust proxy to "loopback"', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
proxySecrets: ['s1'],
trustProxy: 'loopback',
});
expect(config.trustProxy).toBe('loopback');
});
test('should set trust proxy via env var', () => {
process.env.TRUST_PROXY = 'true';
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
proxySecrets: ['s1'],
});
expect(config.trustProxy).toBe(true);
delete process.env.TRUST_PROXY;
});
test('should allow options via env', () => {
process.env.UNLEASH_URL = 'some';
process.env.UNLEASH_API_TOKEN = 'token';
process.env.UNLEASH_PROXY_CLIENT_KEYS = 's1';
process.env.UNLEASH_INSTANCE_ID = 'i1';
const config = createProxyConfig({});
expect(config.unleashUrl).toBe('some');
expect(config.unleashApiToken).toBe('token');
expect(config.clientKeys.length).toBe(1);
expect(config.clientKeys[0]).toBe('s1');
expect(config.unleashInstanceId).toBe('i1');
// cleanup
delete process.env.UNLEASH_URL;
delete process.env.UNLEASH_API_TOKEN;
delete process.env.UNLEASH_PROXY_CLIENT_KEYS;
delete process.env.UNLEASH_INSTANCE_ID;
});
test('should allow old "UNLEASH_PROXY_SECRETS" option via env', () => {
process.env.UNLEASH_URL = 'some';
process.env.UNLEASH_API_TOKEN = 'token';
process.env.UNLEASH_PROXY_SECRETS = 's1-token, s2-token';
const config = createProxyConfig({});
expect(config.unleashUrl).toBe('some');
expect(config.unleashApiToken).toBe('token');
expect(config.clientKeys.length).toBe(2);
expect(config.clientKeys[0]).toBe('s1-token');
expect(config.clientKeys[1]).toBe('s2-token');
// cleanup
delete process.env.UNLEASH_URL;
delete process.env.UNLEASH_API_TOKEN;
delete process.env.UNLEASH_PROXY_SECRETS;
});
test('should load custom activation strategy', () => {
class TestStrat extends Strategy {
constructor() {
super('TestStrat');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
isEnabled(parameters: any, context: any) {
return true;
}
}
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
customStrategies: [new TestStrat()],
});
expect(config.customStrategies?.length).toBe(1);
if (config.customStrategies) {
expect(config.customStrategies[0].name).toBe('TestStrat');
} else {
throw new Error('Expected custom strategy to be set!');
}
});
test('should load custom activation strategy from file', () => {
const base = path.resolve('');
process.env.UNLEASH_CUSTOM_STRATEGIES_FILE = `${base}/src/examples/custom-strategies.js`;
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.customStrategies?.length).toBe(1);
if (config.customStrategies) {
expect(config.customStrategies[0].name).toBe('FromFile');
} else {
throw new Error('Expected custom strategy to be set!');
}
delete process.env.UNLEASH_CUSTOM_STRATEGIES_FILE;
});
test('should set namePrefix via options', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
namePrefix: 'somePrefix',
clientKeys: ['s1'],
});
expect(config.namePrefix).toBe('somePrefix');
});
test('should set namePrefix via env', () => {
process.env.UNLEASH_NAME_PREFIX = 'prefixViaEnv';
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.namePrefix).toBe('prefixViaEnv');
delete process.env.UNLEASH_CUSTOM_STRATEGIES_FILE;
});
test('should set storageProvider via options', () => {
class FakeStorage implements StorageProvider<ClientFeaturesResponse> {
async set(): Promise<void> {
return void 0;
}
async get(): Promise<ClientFeaturesResponse> {
return void 0 as unknown as ClientFeaturesResponse;
}
}
const fakeStorage = new FakeStorage();
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
storageProvider: fakeStorage,
clientKeys: ['s1'],
});
expect(config.storageProvider).toBe(fakeStorage);
});
test('should not set a storageProvider if none is in config', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.storageProvider).toBeUndefined();
});
test('should not set tags', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
proxySecrets: ['s1'],
});
expect(config.tags).toBeUndefined();
});
test('should set tags via opts', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
proxySecrets: ['s1'],
tags: [{ name: 'simple', value: 'proxy' }],
});
expect(config.tags).toStrictEqual([{ name: 'simple', value: 'proxy' }]);
});
test('should not set tags with empty env var', () => {
process.env.UNLEASH_TAGS = '';
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.tags).toBeUndefined();
delete process.env.UNLEASH_CUSTOM_STRATEGIES_FILE;
});
test('should set tags with env var', () => {
process.env.UNLEASH_TAGS = 'simple:proxy, demo:test';
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.tags).toStrictEqual([
{ name: 'simple', value: 'proxy' },
{ name: 'demo', value: 'test' },
]);
delete process.env.UNLEASH_CUSTOM_STRATEGIES_FILE;
});
test('should read serverSideSdkConfig from env vars', () => {
process.env.EXP_SERVER_SIDE_SDK_CONFIG_TOKENS = 'super1, super2';
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.serverSideSdkConfig?.tokens).toStrictEqual([
'super1',
'super2',
]);
delete process.env.EXP_SERVER_SIDE_SDK_CONFIG_TOKENS;
});
test('should read bootstrap from env vars', () => {
process.env.EXP_BOOTSTRAP_URL = 'https://boostrap.unleash.run';
process.env.EXP_BOOTSTRAP_AUTHORIZATION = 'AUTH-BOOTSTRAP';
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.bootstrap).toStrictEqual({
url: 'https://boostrap.unleash.run',
urlHeaders: { Authorization: 'AUTH-BOOTSTRAP' },
});
delete process.env.EXP_BOOTSTRAP_URL;
delete process.env.EXP_BOOTSTRAP_AUTHORIZATION;
});
test('should load cors origin and max age from env', () => {
process.env.CORS_ORIGIN = 'https://my-custom-domain.com/test';
process.env.CORS_METHODS = 'GET, POST';
process.env.CORS_ALLOWED_HEADERS = 'X-Custom-Header';
process.env.CORS_EXPOSED_HEADERS = 'ETag, X-Custom-Header';
process.env.CORS_CREDENTIALS = 'true';
process.env.CORS_MAX_AGE = '1234567';
process.env.CORS_PREFLIGHT_CONTINUE = 'true';
process.env.CORS_OPTIONS_SUCCESS_STATUS = '200';
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.cors.origin).toBe('https://my-custom-domain.com/test');
expect(config.cors.methods).toStrictEqual('GET, POST');
expect(config.cors.allowedHeaders).toStrictEqual('X-Custom-Header');
expect(config.cors.exposedHeaders).toStrictEqual('ETag, X-Custom-Header');
expect(config.cors.credentials).toBe(true);
expect(config.cors.maxAge).toBe(1234567);
expect(config.cors.preflightContinue).toBe(true);
expect(config.cors.optionsSuccessStatus).toBe(200);
delete process.env.CORS_ORIGIN;
delete process.env.CORS_METHODS;
delete process.env.CORS_ALLOWED_HEADERS;
delete process.env.CORS_EXPOSED_HEADERS;
delete process.env.CORS_CREDENTIALS;
delete process.env.CORS_MAX_AGE;
delete process.env.CORS_PREFLIGHT_CONTINUE;
delete process.env.CORS_OPTIONS_SUCCESS_STATUS;
});
test('should load cors options provided', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
cors: {
origin: ['https://test.com/x', 'http://example.com'],
maxAge: 9876,
optionsSuccessStatus: 418,
},
});
expect(config.cors.origin).toStrictEqual([
'https://test.com/x',
'http://example.com',
]);
expect(config.cors.maxAge).toBe(9876);
expect(config.cors.optionsSuccessStatus).toBe(418);
});
test('should transform comma-separated list of urls from env and set cors origin as an array', () => {
process.env.CORS_ORIGIN =
'https://my-custom-domain.com,https://example.com/my-custom-page';
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.cors.origin).toStrictEqual([
'https://my-custom-domain.com',
'https://example.com/my-custom-page',
]);
delete process.env.CORS_ORIGIN;
});
test('should set CORS default values', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.cors.origin).toBe('*');
expect(config.cors.methods).toBe('GET, POST');
expect(config.cors.allowedHeaders).toBeUndefined();
expect(config.cors.exposedHeaders).toBe('ETag');
expect(config.cors.credentials).toBe(false);
expect(config.cors.maxAge).toBe(172800);
expect(config.cors.preflightContinue).toBe(false);
expect(config.cors.optionsSuccessStatus).toBe(204);
});
test('should load the passed-in http agent when config.httpOptions is provided', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
httpOptions: {
agent: () => https.globalAgent,
},
});
expect(config.httpOptions?.agent?.(new URL('https://example.com'))).toBe(
https.globalAgent,
);
});
test('should not set config.httpOptions if no http options are provided at creation', () => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.httpOptions).toBeUndefined();
});
test('should load config.httpOptions.rejectUnauthorized from env', () => {
process.env.HTTP_OPTIONS_REJECT_UNAUTHORIZED = 'true';
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.httpOptions?.rejectUnauthorized).toBe(true);
delete process.env.HTTP_OPTIONS_REJECT_UNAUTHORIZED;
});
test.each([
'/base/path',
'/base/path',
'base/path/',
'base/path',
' base/path ',
])('%s as proxyBasePath should yield /base/path as base path', async (p) => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
proxyBasePath: p,
});
expect(config.proxyBasePath).toBe(`/base/path`);
});
test.each(['', ' ', ' ', undefined])(
`%s as base path should be treated the same as empty string`,
(p) => {
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
proxyBasePath: p,
});
expect(config.proxyBasePath).toBe(``);
},
);