test: split jest config for client and server
This commit is contained in:
parent
d4f507ced3
commit
ce6662bef5
20 changed files with 312 additions and 69 deletions
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
|
@ -7,8 +7,12 @@ env:
|
||||||
JWT_SECRET: "secret"
|
JWT_SECRET: "secret"
|
||||||
ROOT_FOLDER_HOST: /tipi
|
ROOT_FOLDER_HOST: /tipi
|
||||||
APPS_REPO_ID: repo-id
|
APPS_REPO_ID: repo-id
|
||||||
INTERNAL_IP: 192.168.1.10
|
INTERNAL_IP: localhost
|
||||||
|
REDIS_HOST: redis
|
||||||
|
APPS_REPO_URL: https://repo.github.com/
|
||||||
|
DOMAIN: localhost
|
||||||
|
TIPI_VERSION: 0.0.1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci:
|
ci:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -62,10 +66,10 @@ jobs:
|
||||||
|
|
||||||
- name: Run linter
|
- name: Run linter
|
||||||
run: pnpm -r lint
|
run: pnpm -r lint
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: pnpm -r test
|
run: pnpm -r test
|
||||||
|
|
||||||
- uses: codecov/codecov-action@v3
|
- uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,6 +7,8 @@
|
||||||
logs
|
logs
|
||||||
.pnpm-debug.log
|
.pnpm-debug.log
|
||||||
.env*
|
.env*
|
||||||
|
!.env.example
|
||||||
|
!.env.test
|
||||||
github.secrets
|
github.secrets
|
||||||
node_modules/
|
node_modules/
|
||||||
app-data/*
|
app-data/*
|
||||||
|
|
|
@ -29,9 +29,11 @@ module.exports = {
|
||||||
'react/no-unused-prop-types': 0,
|
'react/no-unused-prop-types': 0,
|
||||||
'react/button-has-type': 0,
|
'react/button-has-type': 0,
|
||||||
'import/no-extraneous-dependencies': ['error', { devDependencies: ['**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}', '**/mocks/**', 'tests/**'] }],
|
'import/no-extraneous-dependencies': ['error', { devDependencies: ['**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}', '**/mocks/**', 'tests/**'] }],
|
||||||
|
'no-underscore-dangle': 0,
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
JSX: true,
|
JSX: true,
|
||||||
|
NodeJS: true,
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
'jest/globals': true,
|
'jest/globals': true,
|
||||||
|
|
120
packages/dashboard/__mocks__/fs-extra.ts
Normal file
120
packages/dashboard/__mocks__/fs-extra.ts
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const fs: {
|
||||||
|
__createMockFiles: typeof createMockFiles;
|
||||||
|
__resetAllMocks: typeof resetAllMocks;
|
||||||
|
readFileSync: typeof readFileSync;
|
||||||
|
existsSync: typeof existsSync;
|
||||||
|
writeFileSync: typeof writeFileSync;
|
||||||
|
mkdirSync: typeof mkdirSync;
|
||||||
|
rmSync: typeof rmSync;
|
||||||
|
readdirSync: typeof readdirSync;
|
||||||
|
copyFileSync: typeof copyFileSync;
|
||||||
|
copySync: typeof copyFileSync;
|
||||||
|
createFileSync: typeof createFileSync;
|
||||||
|
unlinkSync: typeof unlinkSync;
|
||||||
|
} = jest.genMockFromModule('fs-extra');
|
||||||
|
|
||||||
|
let mockFiles = Object.create(null);
|
||||||
|
|
||||||
|
const createMockFiles = (newMockFiles: Record<string, string>) => {
|
||||||
|
mockFiles = Object.create(null);
|
||||||
|
|
||||||
|
// Create folder tree
|
||||||
|
Object.keys(newMockFiles).forEach((file) => {
|
||||||
|
const dir = path.dirname(file);
|
||||||
|
|
||||||
|
if (!mockFiles[dir]) {
|
||||||
|
mockFiles[dir] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
mockFiles[dir].push(path.basename(file));
|
||||||
|
mockFiles[file] = newMockFiles[file];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const readFileSync = (p: string) => mockFiles[p];
|
||||||
|
|
||||||
|
const existsSync = (p: string) => mockFiles[p] !== undefined;
|
||||||
|
|
||||||
|
const writeFileSync = (p: string, data: string | string[]) => {
|
||||||
|
mockFiles[p] = data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mkdirSync = (p: string) => {
|
||||||
|
mockFiles[p] = Object.create(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rmSync = (p: string) => {
|
||||||
|
if (mockFiles[p] instanceof Array) {
|
||||||
|
mockFiles[p].forEach((file: string) => {
|
||||||
|
delete mockFiles[path.join(p, file)];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete mockFiles[p];
|
||||||
|
};
|
||||||
|
|
||||||
|
const readdirSync = (p: string) => {
|
||||||
|
const files: string[] = [];
|
||||||
|
|
||||||
|
const depth = p.split('/').length;
|
||||||
|
|
||||||
|
Object.keys(mockFiles).forEach((file) => {
|
||||||
|
if (file.startsWith(p)) {
|
||||||
|
const fileDepth = file.split('/').length;
|
||||||
|
|
||||||
|
if (fileDepth === depth + 1) {
|
||||||
|
files.push(file.split('/').pop() || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return files;
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyFileSync = (source: string, destination: string) => {
|
||||||
|
mockFiles[destination] = mockFiles[source];
|
||||||
|
};
|
||||||
|
|
||||||
|
const copySync = (source: string, destination: string) => {
|
||||||
|
mockFiles[destination] = mockFiles[source];
|
||||||
|
|
||||||
|
if (mockFiles[source] instanceof Array) {
|
||||||
|
mockFiles[source].forEach((file: string) => {
|
||||||
|
mockFiles[`${destination}/${file}`] = mockFiles[`${source}/${file}`];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createFileSync = (p: string) => {
|
||||||
|
mockFiles[p] = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetAllMocks = () => {
|
||||||
|
mockFiles = Object.create(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const unlinkSync = (p: string) => {
|
||||||
|
if (mockFiles[p] instanceof Array) {
|
||||||
|
mockFiles[p].forEach((file: string) => {
|
||||||
|
delete mockFiles[path.join(p, file)];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
delete mockFiles[p];
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.unlinkSync = unlinkSync;
|
||||||
|
fs.readdirSync = readdirSync;
|
||||||
|
fs.existsSync = existsSync;
|
||||||
|
fs.readFileSync = readFileSync;
|
||||||
|
fs.writeFileSync = writeFileSync;
|
||||||
|
fs.mkdirSync = mkdirSync;
|
||||||
|
fs.rmSync = rmSync;
|
||||||
|
fs.copyFileSync = copyFileSync;
|
||||||
|
fs.copySync = copySync;
|
||||||
|
fs.createFileSync = createFileSync;
|
||||||
|
fs.__createMockFiles = createMockFiles;
|
||||||
|
fs.__resetAllMocks = resetAllMocks;
|
||||||
|
|
||||||
|
export default fs;
|
16
packages/dashboard/__mocks__/redis.ts
Normal file
16
packages/dashboard/__mocks__/redis.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export const createClient = jest.fn(() => {
|
||||||
|
const values = new Map();
|
||||||
|
const expirations = new Map();
|
||||||
|
return {
|
||||||
|
isOpen: true,
|
||||||
|
connect: jest.fn(),
|
||||||
|
set: (key: string, value: string, exp: number) => {
|
||||||
|
values.set(key, value);
|
||||||
|
expirations.set(key, exp);
|
||||||
|
},
|
||||||
|
get: (key: string) => values.get(key),
|
||||||
|
quit: jest.fn(),
|
||||||
|
del: (key: string) => values.delete(key),
|
||||||
|
ttl: (key: string) => expirations.get(key),
|
||||||
|
};
|
||||||
|
});
|
|
@ -1,18 +0,0 @@
|
||||||
const nextJest = require('next/jest');
|
|
||||||
|
|
||||||
const createJestConfig = nextJest({
|
|
||||||
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
|
|
||||||
dir: './',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add any custom config to be passed to Jest
|
|
||||||
const customJestConfig = {
|
|
||||||
setupFilesAfterEnv: ['<rootDir>/tests/jest.setup.tsx'],
|
|
||||||
testEnvironment: 'jest-environment-jsdom',
|
|
||||||
collectCoverage: true,
|
|
||||||
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/index.ts', '!**/src/pages/**/*.{ts,tsx}', '!**/src/mocks/**', '!**/src/core/apollo/**'],
|
|
||||||
testMatch: ['<rootDir>/src/**/*.{spec,test}.{ts,tsx}'],
|
|
||||||
};
|
|
||||||
|
|
||||||
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
|
|
||||||
module.exports = createJestConfig(customJestConfig);
|
|
39
packages/dashboard/jest.config.ts
Normal file
39
packages/dashboard/jest.config.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import nextJest from 'next/jest';
|
||||||
|
|
||||||
|
const createJestConfig = nextJest({
|
||||||
|
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
|
||||||
|
dir: './',
|
||||||
|
});
|
||||||
|
|
||||||
|
const customClientConfig = {
|
||||||
|
testEnvironment: 'jest-environment-jsdom',
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/tests/client/jest.setup.tsx'],
|
||||||
|
testMatch: ['<rootDir>/src/client/**/*.{spec,test}.{ts,tsx}', '!<rootDir>/src/server/**/*.{spec,test}.{ts,tsx}'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const customServerConfig = {
|
||||||
|
testEnvironment: 'node',
|
||||||
|
testMatch: ['<rootDir>/src/server/**/*.test.ts'],
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/tests/server/jest.setup.ts'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async () => {
|
||||||
|
const clientConfig = await createJestConfig(customClientConfig)();
|
||||||
|
const serverConfig = await createJestConfig(customServerConfig)();
|
||||||
|
|
||||||
|
return {
|
||||||
|
verbose: true,
|
||||||
|
collectCoverage: true,
|
||||||
|
collectCoverageFrom: ['src/server/**/*.{ts,tsx}', 'src/client/**/*.{ts,tsx}', '!src/**/mocks/**/*.{ts,tsx}', '!**/*.{spec,test}.{ts,tsx}', '!**/index.{ts,tsx}'],
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
displayName: 'client',
|
||||||
|
...clientConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'server',
|
||||||
|
...serverConfig,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,8 +0,0 @@
|
||||||
/** @type {import('next').NextConfig} */
|
|
||||||
const nextConfig = {
|
|
||||||
output: 'standalone',
|
|
||||||
reactStrictMode: true,
|
|
||||||
swcMinify: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = nextConfig;
|
|
23
packages/dashboard/next.config.mjs
Normal file
23
packages/dashboard/next.config.mjs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
swcMinify: true,
|
||||||
|
output: 'standalone',
|
||||||
|
reactStrictMode: true,
|
||||||
|
serverRuntimeConfig: {
|
||||||
|
INTERNAL_IP: process.env.INTERNAL_IP,
|
||||||
|
TIPI_VERSION: process.env.TIPI_VERSION,
|
||||||
|
JWT_SECRET: process.env.JWT_SECRET,
|
||||||
|
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
|
||||||
|
POSTGRES_USERNAME: process.env.POSTGRES_USERNAME,
|
||||||
|
POSTGRES_DBNAME: process.env.POSTGRES_DBNAME,
|
||||||
|
POSTGRES_HOST: process.env.POSTGRES_HOST,
|
||||||
|
APPS_REPO_ID: process.env.APPS_REPO_ID,
|
||||||
|
APPS_REPO_URL: process.env.APPS_REPO_URL,
|
||||||
|
DOMAIN: process.env.DOMAIN,
|
||||||
|
ARCHITECTURE: process.env.ARCHITECTURE,
|
||||||
|
NODE_ENV: process.env.NODE_ENV,
|
||||||
|
REDIS_HOST: process.env.REDIS_HOST,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
|
@ -1,6 +1,5 @@
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import axios from 'axios';
|
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import { SystemService } from '.';
|
import { SystemService } from '.';
|
||||||
import { EventDispatcher } from '../../core/EventDispatcher';
|
import { EventDispatcher } from '../../core/EventDispatcher';
|
||||||
|
@ -66,9 +65,8 @@ describe('Test: getVersion', () => {
|
||||||
|
|
||||||
it('It should return version', async () => {
|
it('It should return version', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const spy = jest.spyOn(axios, 'get').mockResolvedValue({
|
// @ts-expect-error Mocking fetch
|
||||||
data: { name: `v${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}` },
|
fetch.mockImplementationOnce(() => Promise.resolve({ json: () => Promise.resolve({ name: `v${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}` }) }));
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const version = await SystemService.getVersion();
|
const version = await SystemService.getVersion();
|
||||||
|
@ -77,14 +75,11 @@ describe('Test: getVersion', () => {
|
||||||
expect(version).toBeDefined();
|
expect(version).toBeDefined();
|
||||||
expect(version.current).toBeDefined();
|
expect(version.current).toBeDefined();
|
||||||
expect(semver.valid(version.latest)).toBeTruthy();
|
expect(semver.valid(version.latest)).toBeTruthy();
|
||||||
|
|
||||||
spy.mockRestore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should return undefined for latest if request fails', async () => {
|
it('Should return undefined for latest if request fails', async () => {
|
||||||
jest.spyOn(axios, 'get').mockImplementation(() => {
|
// @ts-expect-error Mocking fetch
|
||||||
throw new Error('Error');
|
fetch.mockImplementationOnce(() => Promise.reject(new Error('API is down')));
|
||||||
});
|
|
||||||
|
|
||||||
const version = await SystemService.getVersion();
|
const version = await SystemService.getVersion();
|
||||||
|
|
||||||
|
@ -95,9 +90,8 @@ describe('Test: getVersion', () => {
|
||||||
|
|
||||||
it('Should return cached version', async () => {
|
it('Should return cached version', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const spy = jest.spyOn(axios, 'get').mockResolvedValue({
|
// @ts-expect-error Mocking fetch
|
||||||
data: { name: `v${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}` },
|
fetch.mockImplementationOnce(() => Promise.resolve({ json: () => Promise.resolve({ name: `v${faker.random.numeric(1)}.${faker.random.numeric(1)}.${faker.random.numeric()}` }) }));
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const version = await SystemService.getVersion();
|
const version = await SystemService.getVersion();
|
||||||
|
@ -111,10 +105,6 @@ describe('Test: getVersion', () => {
|
||||||
expect(version2.latest).toBe(version.latest);
|
expect(version2.latest).toBe(version.latest);
|
||||||
expect(version2.current).toBeDefined();
|
expect(version2.current).toBeDefined();
|
||||||
expect(semver.valid(version2.latest)).toBeTruthy();
|
expect(semver.valid(version2.latest)).toBeTruthy();
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
spy.mockRestore();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -148,14 +138,12 @@ describe('Test: update', () => {
|
||||||
it('Should throw an error if latest version is not set', async () => {
|
it('Should throw an error if latest version is not set', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
TipiCache.del('latestVersion');
|
TipiCache.del('latestVersion');
|
||||||
const spy = jest.spyOn(axios, 'get').mockResolvedValue({
|
// @ts-expect-error Mocking fetch
|
||||||
data: { name: null },
|
fetch.mockImplementationOnce(() => Promise.resolve({ json: () => Promise.resolve({ name: null }) }));
|
||||||
});
|
|
||||||
setConfig('version', '0.0.1');
|
setConfig('version', '0.0.1');
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
await expect(SystemService.update()).rejects.toThrow('Could not get latest version');
|
await expect(SystemService.update()).rejects.toThrow('Could not get latest version');
|
||||||
spy.mockRestore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should throw if current version is higher than latest', async () => {
|
it('Should throw if current version is higher than latest', async () => {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import axios from 'axios';
|
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { readJsonFile } from '../../common/fs.helpers';
|
import { readJsonFile } from '../../common/fs.helpers';
|
||||||
|
@ -6,7 +5,6 @@ import { EventDispatcher, EventTypes } from '../../core/EventDispatcher';
|
||||||
import { Logger } from '../../core/Logger';
|
import { Logger } from '../../core/Logger';
|
||||||
import TipiCache from '../../core/TipiCache';
|
import TipiCache from '../../core/TipiCache';
|
||||||
import { getConfig, setConfig } from '../../core/TipiConfig';
|
import { getConfig, setConfig } from '../../core/TipiConfig';
|
||||||
import { env } from '../../../env/server.mjs';
|
|
||||||
import { SystemStatus } from '../../../client/state/systemStore';
|
import { SystemStatus } from '../../../client/state/systemStore';
|
||||||
|
|
||||||
const systemInfoSchema = z.object({
|
const systemInfoSchema = z.object({
|
||||||
|
@ -38,9 +36,10 @@ const getVersion = async (): Promise<{ current: string; latest?: string }> => {
|
||||||
let version = await TipiCache.get('latestVersion');
|
let version = await TipiCache.get('latestVersion');
|
||||||
|
|
||||||
if (!version) {
|
if (!version) {
|
||||||
const { data } = await axios.get('https://api.github.com/repos/meienberger/runtipi/releases/latest');
|
const data = await fetch('https://api.github.com/repos/meienberger/runtipi/releases/latest');
|
||||||
|
const release = await data.json();
|
||||||
|
|
||||||
version = data.name.replace('v', '');
|
version = release.name.replace('v', '');
|
||||||
await TipiCache.set('latestVersion', version?.replace('v', '') || '', 60 * 60);
|
await TipiCache.set('latestVersion', version?.replace('v', '') || '', 60 * 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +61,7 @@ const systemInfo = (): z.infer<typeof systemInfoSchema> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const restart = async (): Promise<boolean> => {
|
const restart = async (): Promise<boolean> => {
|
||||||
if (env.NODE_ENV === 'development') {
|
if (getConfig().NODE_ENV === 'development') {
|
||||||
throw new Error('Cannot restart in development mode');
|
throw new Error('Cannot restart in development mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +74,7 @@ const restart = async (): Promise<boolean> => {
|
||||||
const update = async (): Promise<boolean> => {
|
const update = async (): Promise<boolean> => {
|
||||||
const { current, latest } = await getVersion();
|
const { current, latest } = await getVersion();
|
||||||
|
|
||||||
if (env.NODE_ENV === 'development') {
|
if (getConfig().NODE_ENV === 'development') {
|
||||||
throw new Error('Cannot update in development mode');
|
throw new Error('Cannot update in development mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
49
packages/dashboard/tests/TRPCTestClientProvider.tsx
Normal file
49
packages/dashboard/tests/TRPCTestClientProvider.tsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import { createTRPCReact, httpBatchLink, loggerLink } from '@trpc/react-query';
|
||||||
|
import SuperJSON from 'superjson';
|
||||||
|
import React from 'react';
|
||||||
|
import fetch from 'isomorphic-fetch';
|
||||||
|
|
||||||
|
import type { AppRouter } from '../src/server/routers/_app';
|
||||||
|
|
||||||
|
export const trpc = createTRPCReact<AppRouter>({
|
||||||
|
unstable_overrides: {
|
||||||
|
useMutation: {
|
||||||
|
async onSuccess(opts) {
|
||||||
|
await opts.originalFn();
|
||||||
|
await opts.queryClient.invalidateQueries();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
const trpcClient = trpc.createClient({
|
||||||
|
links: [
|
||||||
|
loggerLink({
|
||||||
|
enabled: () => false,
|
||||||
|
}),
|
||||||
|
httpBatchLink({
|
||||||
|
url: 'http://localhost:3000/api/trpc',
|
||||||
|
headers() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
fetch: async (input, init?) =>
|
||||||
|
fetch(input, {
|
||||||
|
...init,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
transformer: SuperJSON,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export function TRPCTestClientProvider(props: { children: React.ReactNode }) {
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
||||||
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||||
|
</trpc.Provider>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import '@testing-library/jest-dom/extend-expect';
|
import '@testing-library/jest-dom/extend-expect';
|
||||||
import 'whatwg-fetch';
|
import 'whatwg-fetch';
|
||||||
import { server } from '../src/client/mocks/server';
|
import { server } from '../../src/client/mocks/server';
|
||||||
import { mockApolloClient } from './test-utils';
|
import { mockApolloClient } from '../test-utils';
|
||||||
import { useToastStore } from '../src/client/state/toastStore';
|
import { useToastStore } from '../../src/client/state/toastStore';
|
||||||
|
|
||||||
// Mock next/router
|
// Mock next/router
|
||||||
// eslint-disable-next-line global-require
|
// eslint-disable-next-line global-require
|
20
packages/dashboard/tests/server/jest.setup.ts
Normal file
20
packages/dashboard/tests/server/jest.setup.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { EventDispatcher } from '../../src/server/core/EventDispatcher';
|
||||||
|
|
||||||
|
global.fetch = jest.fn();
|
||||||
|
// Mock Logger
|
||||||
|
jest.mock('../../src/server/core/Logger', () => ({
|
||||||
|
Logger: {
|
||||||
|
info: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('next/config', () => () => ({
|
||||||
|
serverRuntimeConfig: {
|
||||||
|
...process.env,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
EventDispatcher.clearInterval();
|
||||||
|
});
|
|
@ -3,6 +3,7 @@ import { render, RenderOptions, renderHook } from '@testing-library/react';
|
||||||
import { ApolloClient, ApolloProvider, HttpLink, InMemoryCache } from '@apollo/client';
|
import { ApolloClient, ApolloProvider, HttpLink, InMemoryCache } from '@apollo/client';
|
||||||
import fetch from 'isomorphic-fetch';
|
import fetch from 'isomorphic-fetch';
|
||||||
import { SWRConfig } from 'swr';
|
import { SWRConfig } from 'swr';
|
||||||
|
import { TRPCTestClientProvider } from './TRPCTestClientProvider';
|
||||||
|
|
||||||
const link = new HttpLink({
|
const link = new HttpLink({
|
||||||
uri: 'http://localhost:3000/graphql',
|
uri: 'http://localhost:3000/graphql',
|
||||||
|
@ -18,9 +19,11 @@ export const mockApolloClient = new ApolloClient({
|
||||||
});
|
});
|
||||||
|
|
||||||
const AllTheProviders: FC<{ children: React.ReactNode }> = ({ children }) => (
|
const AllTheProviders: FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
<SWRConfig value={{ dedupingInterval: 0, provider: () => new Map() }}>
|
<TRPCTestClientProvider>
|
||||||
<ApolloProvider client={mockApolloClient}>{children}</ApolloProvider>
|
<SWRConfig value={{ dedupingInterval: 0, provider: () => new Map() }}>
|
||||||
</SWRConfig>
|
<ApolloProvider client={mockApolloClient}>{children}</ApolloProvider>
|
||||||
|
</SWRConfig>
|
||||||
|
</TRPCTestClientProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => render(ui, { wrapper: AllTheProviders, ...options });
|
const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => render(ui, { wrapper: AllTheProviders, ...options });
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es2017",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
@ -16,8 +16,9 @@
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
"types": ["jest", "@testing-library/jest-dom"]
|
"types": ["jest", "@testing-library/jest-dom"]
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.mjs", "**/*.js", "**/*.jsx"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|
3
packages/system-api/.env.test
Normal file
3
packages/system-api/.env.test
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
APPS_REPO_ID=repo-id
|
||||||
|
INTERNAL_IP=localhost
|
||||||
|
JWT_SECRET=secret
|
|
@ -21,7 +21,7 @@ describe('Test: getConfig', () => {
|
||||||
expect(config.logs.LOGS_ERROR).toBe('error.log');
|
expect(config.logs.LOGS_ERROR).toBe('error.log');
|
||||||
expect(config.dnsIp).toBe('9.9.9.9');
|
expect(config.dnsIp).toBe('9.9.9.9');
|
||||||
expect(config.rootFolder).toBe('/runtipi');
|
expect(config.rootFolder).toBe('/runtipi');
|
||||||
expect(config.internalIp).toBe('192.168.1.10');
|
expect(config.internalIp).toBe('localhost');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -254,7 +254,7 @@ describe('Test: generateEnvFile', () => {
|
||||||
const envmap = await getEnvMap(appInfo.id);
|
const envmap = await getEnvMap(appInfo.id);
|
||||||
|
|
||||||
expect(envmap.get('APP_EXPOSED')).toBeUndefined();
|
expect(envmap.get('APP_EXPOSED')).toBeUndefined();
|
||||||
expect(envmap.get('APP_DOMAIN')).toBe(`192.168.1.10:${appInfo.port}`);
|
expect(envmap.get('APP_DOMAIN')).toBe(`localhost:${appInfo.port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should create app folder if it does not exist', async () => {
|
it('Should create app folder if it does not exist', async () => {
|
||||||
|
|
|
@ -47,7 +47,7 @@ describe('Install app', () => {
|
||||||
await AppsService.installApp(app1.id, { TEST_FIELD: 'test' });
|
await AppsService.installApp(app1.id, { TEST_FIELD: 'test' });
|
||||||
const envFile = fs.readFileSync(`/app/storage/app-data/${app1.id}/app.env`).toString();
|
const envFile = fs.readFileSync(`/app/storage/app-data/${app1.id}/app.env`).toString();
|
||||||
|
|
||||||
expect(envFile.trim()).toBe(`TEST=test\nAPP_PORT=${app1.port}\nTEST_FIELD=test\nAPP_DOMAIN=192.168.1.10:${app1.port}`);
|
expect(envFile.trim()).toBe(`TEST=test\nAPP_PORT=${app1.port}\nTEST_FIELD=test\nAPP_DOMAIN=localhost:${app1.port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should add app in database', async () => {
|
it('Should add app in database', async () => {
|
||||||
|
@ -315,7 +315,7 @@ describe('Start app', () => {
|
||||||
|
|
||||||
const envFile = fs.readFileSync(`/app/storage/app-data/${app1.id}/app.env`).toString();
|
const envFile = fs.readFileSync(`/app/storage/app-data/${app1.id}/app.env`).toString();
|
||||||
|
|
||||||
expect(envFile.trim()).toBe(`TEST=test\nAPP_PORT=${app1.port}\nTEST_FIELD=test\nAPP_DOMAIN=192.168.1.10:${app1.port}`);
|
expect(envFile.trim()).toBe(`TEST=test\nAPP_PORT=${app1.port}\nTEST_FIELD=test\nAPP_DOMAIN=localhost:${app1.port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should throw if start script fails', async () => {
|
it('Should throw if start script fails', async () => {
|
||||||
|
@ -377,7 +377,7 @@ describe('Update app config', () => {
|
||||||
|
|
||||||
const envFile = fs.readFileSync(`/app/storage/app-data/${app1.id}/app.env`).toString();
|
const envFile = fs.readFileSync(`/app/storage/app-data/${app1.id}/app.env`).toString();
|
||||||
|
|
||||||
expect(envFile.trim()).toBe(`TEST=test\nAPP_PORT=${app1.port}\nTEST_FIELD=test\nAPP_DOMAIN=192.168.1.10:${app1.port}`);
|
expect(envFile.trim()).toBe(`TEST=test\nAPP_PORT=${app1.port}\nTEST_FIELD=test\nAPP_DOMAIN=localhost:${app1.port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should throw if required field is missing', async () => {
|
it('Should throw if required field is missing', async () => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue