Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions .github/workflows/lint-test.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
name: Lint and test
# This workflow is triggered on pushes to the repository.

on:
pull_request:
push:
branches:
- '**'
- master

jobs:
lint:
name: Linting
runs-on: ubuntu-latest

container:
image: node:alpine
# Node is pinned to 22 to match the runtime image (see Dockerfile);
# @nestjs/bull 0.1.x is incompatible with Node >= 23.
image: node:22-alpine

steps:
- uses: actions/checkout@v2
- run: yarn
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn run lint:ci --max-warnings 1

tests:
Expand All @@ -24,25 +27,22 @@ jobs:
runs-on: ubuntu-latest

container:
image: node:lts-alpine
image: node:22-alpine

steps:
- uses: actions/checkout@v2
- run: yarn
- run: yarn run test
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn run test:cov

build:
needs: [lint, tests]
name: Build
runs-on: ubuntu-latest
# if: github.event_name == 'push' && github.ref == 'refs/heads/master'

container:
image: node:lts-alpine
image: node:22-alpine

steps:
- uses: actions/checkout@v2
- run: yarn
- run: npm install -g typescript@3.8.3
- run: tsc --version
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn run build
7 changes: 7 additions & 0 deletions .yarn-offline-mirror/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Only the zencrepes.zindexer (bit.dev) tarballs are vendored: the bit.dev
# registry is gone, so they cannot be fetched anymore. Everything else is
# available from the npm registry and does not need to be committed, even
# though yarn copies every downloaded tarball into this mirror.
*
!.gitignore
!zencrepes.zindexer.*.tgz
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions .yarnrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
yarn-offline-mirror "./.yarn-offline-mirror"
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@
"ts"
],
"rootDir": "src",
"setupFiles": ["<rootDir>/../test/jest.setup.js"],
"testRegex": ".spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
"^.+\\.ts$": "ts-jest"
},
"coverageDirectory": "../coverage",
"testEnvironment": "node"
Expand Down
96 changes: 96 additions & 0 deletions src/config.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Logger } from '@nestjs/common';
import * as fs from 'fs';
import * as jsYaml from 'js-yaml';
import * as os from 'os';
import * as path from 'path';

import { ConfigService } from './config.service';

describe('ConfigService', () => {
let configDir: string;
let exitSpy: jest.SpyInstance;
const initialEnv = { ...process.env };

beforeAll(() => {
jest.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined);
jest.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined);
});

beforeEach(() => {
configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zqueue-config-'));
process.env.CONFIG_PATH = configDir;
delete process.env.APP_VERSION;
exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
});

afterEach(() => {
exitSpy.mockRestore();
process.env = { ...initialEnv };
});

it('initializes a default configuration file and exits when none exists', () => {
expect(() => new ConfigService()).toThrow('process.exit called');

expect(exitSpy).toHaveBeenCalledWith(1);
const configFile = path.join(configDir, 'config.yml');
expect(fs.existsSync(configFile)).toBe(true);
const written = jsYaml.safeLoad(fs.readFileSync(configFile, 'utf8'));
expect(written).toHaveProperty('elasticsearch');
expect(written).toHaveProperty('github');
});

it('loads an existing configuration file', () => {
const userConfig = {
elasticsearch: { host: 'http://127.0.0.1:9200' },
github: { webhook: { secret: 'fake-secret' } },
};
fs.writeFileSync(
path.join(configDir, 'config.yml'),
jsYaml.safeDump(userConfig),
);

const service = new ConfigService();

expect(exitSpy).not.toHaveBeenCalled();
expect(service.getUserConfig()).toEqual(userConfig);
});

it('exposes the environment configuration through get()', () => {
fs.writeFileSync(
path.join(configDir, 'config.yml'),
jsYaml.safeDump({ elasticsearch: {} }),
);

const service = new ConfigService();

expect(service.get('CONFIG_DIR')).toEqual(configDir);
expect(service.get('APP_VERSION')).toEqual('develop');
});

it('reads the application version from the environment', () => {
process.env.APP_VERSION = '1.2.3';
fs.writeFileSync(
path.join(configDir, 'config.yml'),
jsYaml.safeDump({ elasticsearch: {} }),
);

const service = new ConfigService();

expect(service.get('APP_VERSION')).toEqual('1.2.3');
});

it('allows replacing the user configuration', () => {
fs.writeFileSync(
path.join(configDir, 'config.yml'),
jsYaml.safeDump({ elasticsearch: {} }),
);
const service = new ConfigService();
const newConfig = { elasticsearch: { host: 'http://other:9200' } } as any;

service.setUserConfig(newConfig);

expect(service.getUserConfig()).toEqual(newConfig);
});
});
91 changes: 91 additions & 0 deletions src/esClient.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Logger } from '@nestjs/common';
import { Client } from '@elastic/elasticsearch';
import * as fs from 'fs';

import { EsClientService } from './esClient.service';
import { ConfigService } from './config.service';

jest.mock('@elastic/elasticsearch', () => ({
Client: jest.fn(),
}));

const buildConfigService = (elasticsearch) =>
({
getUserConfig: () => ({ elasticsearch }),
} as ConfigService);

describe('EsClientService', () => {
beforeAll(() => {
jest.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined);
});

beforeEach(() => {
jest.clearAllMocks();
});

it('connects to Elastic Cloud when cloud credentials are provided', () => {
new EsClientService(
buildConfigService({
cloudId: 'deployment:abcdef',
username: 'elastic',
password: 'fake-password',
host: 'http://127.0.0.1:9200',
}),
);

expect(Client).toHaveBeenCalledWith({
cloud: {
id: 'deployment:abcdef',
username: 'elastic',
password: 'fake-password',
},
});
});

it('connects with an SSL certificate authority when configured', () => {
const readFileSyncSpy = jest
.spyOn(fs, 'readFileSync')
.mockReturnValue('FAKE-CA-CONTENT');

new EsClientService(
buildConfigService({
host: 'https://es.internal:9200',
sslCa: '/path/to/ca.pem',
}),
);

expect(readFileSyncSpy).toHaveBeenCalledWith('/path/to/ca.pem');
expect(Client).toHaveBeenCalledWith({
node: 'https://es.internal:9200',
ssl: { ca: 'FAKE-CA-CONTENT' },
});
readFileSyncSpy.mockRestore();
});

it('connects to a plain host when no cloud or ssl settings are provided', () => {
new EsClientService(buildConfigService({ host: 'http://127.0.0.1:9200' }));

expect(Client).toHaveBeenCalledWith({ node: 'http://127.0.0.1:9200' });
});

it('ignores incomplete cloud credentials', () => {
new EsClientService(
buildConfigService({
cloudId: 'deployment:abcdef',
username: '',
password: '',
host: 'http://127.0.0.1:9200',
}),
);

expect(Client).toHaveBeenCalledWith({ node: 'http://127.0.0.1:9200' });
});

it('exposes the created client', () => {
const service = new EsClientService(
buildConfigService({ host: 'http://127.0.0.1:9200' }),
);

expect(service.getEsClient()).toBe((Client as jest.Mock).mock.instances[0]);
});
});
Loading
Loading