mirror of
https://github.com/meienberger/runtipi.git
synced 2024-09-11 12:15:48 +03:00
test: fix paths
This commit is contained in:
parent
6c5381954b
commit
83c163fa72
@ -84,7 +84,7 @@ services:
|
||||
# Static
|
||||
- ./.env:/data/.env
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /:/mnt/host:ro
|
||||
- /proc:/host/proc:ro
|
||||
networks:
|
||||
- tipi_main_network
|
||||
ports:
|
||||
@ -145,6 +145,7 @@ services:
|
||||
traefik.http.routers.worker-local.service: worker
|
||||
traefik.http.routers.worker-api-local.rule: Host(`${LOCAL_DOMAIN}`) && PathPrefix("/worker-api")
|
||||
traefik.http.routers.worker-api-local.entrypoints: websecure
|
||||
traefik.http.routers.worker-api-local.service: worker-api
|
||||
traefik.http.routers.worker-api-local.tls: true
|
||||
|
||||
networks:
|
||||
|
@ -80,7 +80,7 @@ services:
|
||||
# Static
|
||||
- ./.env:/data/.env
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /:/mnt/host:ro
|
||||
- /proc:/host/proc
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
TIPI_VERSION: development
|
||||
@ -138,6 +138,7 @@ services:
|
||||
traefik.http.routers.worker-api-local.rule: Host(`${LOCAL_DOMAIN}`) && PathPrefix("/worker-api")
|
||||
traefik.http.routers.worker-api-local.entrypoints: websecure
|
||||
traefik.http.routers.worker-api-local.tls: true
|
||||
traefik.http.routers.worker-api-local.service: worker-api
|
||||
|
||||
networks:
|
||||
tipi_main_network:
|
||||
|
@ -4,6 +4,7 @@ import { vi, it, describe, beforeEach, expect } from 'vitest';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import fs from 'fs';
|
||||
import { compose } from './docker-helpers';
|
||||
import { APP_DATA_DIR, DATA_DIR } from '@/config/constants';
|
||||
|
||||
const execAsync = vi.fn().mockImplementation(() => Promise.resolve({ stdout: '', stderr: '' }));
|
||||
|
||||
@ -39,10 +40,10 @@ describe('docker helpers', async () => {
|
||||
// assert
|
||||
const expected = [
|
||||
'docker-compose',
|
||||
`--env-file /storage/app-data/${appId}/app.env`,
|
||||
`--env-file ${APP_DATA_DIR}/${appId}/app.env`,
|
||||
`--project-name ${appId}`,
|
||||
`-f /app/apps/${appId}/docker-compose.yml`,
|
||||
'-f /app/repos/repo-id/apps/docker-compose.common.yml',
|
||||
`-f ${DATA_DIR}/apps/${appId}/docker-compose.yml`,
|
||||
`-f ${DATA_DIR}/repos/repo-id/apps/docker-compose.common.yml`,
|
||||
command,
|
||||
].join(' ');
|
||||
|
||||
@ -53,8 +54,8 @@ describe('docker helpers', async () => {
|
||||
// arrange
|
||||
const appId = faker.word.noun().toLowerCase();
|
||||
const command = faker.word.noun().toLowerCase();
|
||||
await fs.promises.mkdir(`/app/user-config/${appId}`, { recursive: true });
|
||||
const userEnvFile = `/app/user-config/${appId}/app.env`;
|
||||
await fs.promises.mkdir(`${DATA_DIR}/user-config/${appId}`, { recursive: true });
|
||||
const userEnvFile = `${DATA_DIR}/user-config/${appId}/app.env`;
|
||||
await fs.promises.writeFile(userEnvFile, 'test');
|
||||
|
||||
// act
|
||||
@ -63,11 +64,11 @@ describe('docker helpers', async () => {
|
||||
// assert
|
||||
const expected = [
|
||||
'docker-compose',
|
||||
`--env-file /storage/app-data/${appId}/app.env`,
|
||||
`--env-file ${APP_DATA_DIR}/${appId}/app.env`,
|
||||
`--env-file ${userEnvFile}`,
|
||||
`--project-name ${appId}`,
|
||||
`-f /app/apps/${appId}/docker-compose.yml`,
|
||||
'-f /app/repos/repo-id/apps/docker-compose.common.yml',
|
||||
`-f ${DATA_DIR}/apps/${appId}/docker-compose.yml`,
|
||||
`-f ${DATA_DIR}/repos/repo-id/apps/docker-compose.common.yml`,
|
||||
command,
|
||||
].join(' ');
|
||||
|
||||
@ -78,8 +79,8 @@ describe('docker helpers', async () => {
|
||||
// arrange
|
||||
const appId = faker.word.noun().toLowerCase();
|
||||
const command = faker.word.noun().toLowerCase();
|
||||
await fs.promises.mkdir(`/app/user-config/${appId}`, { recursive: true });
|
||||
const userComposeFile = `/app/user-config/${appId}/docker-compose.yml`;
|
||||
await fs.promises.mkdir(`${DATA_DIR}/user-config/${appId}`, { recursive: true });
|
||||
const userComposeFile = `${DATA_DIR}/user-config/${appId}/docker-compose.yml`;
|
||||
await fs.promises.writeFile(userComposeFile, 'test');
|
||||
|
||||
// act
|
||||
@ -88,10 +89,10 @@ describe('docker helpers', async () => {
|
||||
// assert
|
||||
const expected = [
|
||||
'docker-compose',
|
||||
`--env-file /storage/app-data/${appId}/app.env`,
|
||||
`--env-file ${APP_DATA_DIR}/${appId}/app.env`,
|
||||
`--project-name ${appId}`,
|
||||
`-f /app/apps/${appId}/docker-compose.yml`,
|
||||
'-f /app/repos/repo-id/apps/docker-compose.common.yml',
|
||||
`-f ${DATA_DIR}/apps/${appId}/docker-compose.yml`,
|
||||
`-f ${DATA_DIR}/repos/repo-id/apps/docker-compose.common.yml`,
|
||||
`--file ${userComposeFile}`,
|
||||
command,
|
||||
].join(' ');
|
||||
@ -107,8 +108,8 @@ describe('docker helpers', async () => {
|
||||
});
|
||||
const appId = faker.word.noun().toLowerCase();
|
||||
const command = faker.word.noun().toLowerCase();
|
||||
await fs.promises.mkdir(`/app/apps/${appId}`, { recursive: true });
|
||||
const arm64ComposeFile = `/app/apps/${appId}/docker-compose.arm64.yml`;
|
||||
await fs.promises.mkdir(`${DATA_DIR}/apps/${appId}`, { recursive: true });
|
||||
const arm64ComposeFile = `${DATA_DIR}/apps/${appId}/docker-compose.arm64.yml`;
|
||||
await fs.promises.writeFile(arm64ComposeFile, 'test');
|
||||
|
||||
// act
|
||||
@ -117,10 +118,10 @@ describe('docker helpers', async () => {
|
||||
// assert
|
||||
const expected = [
|
||||
'docker-compose',
|
||||
`--env-file /storage/app-data/${appId}/app.env`,
|
||||
`--env-file ${APP_DATA_DIR}/${appId}/app.env`,
|
||||
`--project-name ${appId}`,
|
||||
`-f ${arm64ComposeFile}`,
|
||||
`-f /app/repos/repo-id/apps/docker-compose.common.yml`,
|
||||
`-f ${DATA_DIR}/repos/repo-id/apps/docker-compose.common.yml`,
|
||||
command,
|
||||
].join(' ');
|
||||
|
||||
|
@ -23,7 +23,7 @@ type EnvKeys =
|
||||
| 'NGINX_PORT'
|
||||
| 'NGINX_PORT_SSL'
|
||||
| 'DOMAIN'
|
||||
| 'APP_DATA_DIR'
|
||||
| 'STOTAGE_PATH'
|
||||
| 'POSTGRES_PORT'
|
||||
| 'POSTGRES_HOST'
|
||||
| 'POSTGRES_DBNAME'
|
||||
|
@ -115,7 +115,7 @@ describe('app helpers', () => {
|
||||
it('Should not re-create app-data folder if it already exists', async () => {
|
||||
// arrange
|
||||
const appConfig = createAppConfig({});
|
||||
await fs.promises.mkdir(`${DATA_DIR}/app-data/${appConfig.id}`, { recursive: true });
|
||||
await fs.promises.mkdir(`${APP_DATA_DIR}/${appConfig.id}`, { recursive: true });
|
||||
|
||||
// act
|
||||
await generateEnvFile(appConfig.id, {});
|
||||
|
@ -123,7 +123,7 @@ export class AppExecutors {
|
||||
this.logger.info(`Copying folder ${repoPath} to ${appDirPath}`);
|
||||
await fs.promises.cp(repoPath, appDirPath, { recursive: true });
|
||||
|
||||
// Create folder app-data folder
|
||||
// Create app-data folder
|
||||
this.logger.info(`Creating folder ${appDataDirPath}`);
|
||||
await fs.promises.mkdir(appDataDirPath, { recursive: true });
|
||||
|
||||
|
@ -119,7 +119,8 @@ const renderTemplate = (template: string, envMap: Map<string, string>) => {
|
||||
let renderedTemplate = template;
|
||||
|
||||
envMap.forEach((value, key) => {
|
||||
renderedTemplate = renderedTemplate.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
||||
const safeKey = key.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
renderedTemplate = renderedTemplate.replace(new RegExp(`{{${safeKey}}}`, 'g'), value);
|
||||
});
|
||||
|
||||
return renderedTemplate;
|
||||
|
@ -29,13 +29,13 @@ export class SystemExecutors {
|
||||
const memResult = { total: 0, used: 0, available: 0 };
|
||||
|
||||
try {
|
||||
const memInfo = await fs.promises.readFile('/mnt/host/proc/meminfo');
|
||||
const memInfo = await fs.promises.readFile('/host/proc/meminfo');
|
||||
|
||||
memResult.total = Number(memInfo.toString().match(/MemTotal:\s+(\d+)/)?.[1] ?? 0) * 1024;
|
||||
memResult.available = Number(memInfo.toString().match(/MemAvailable:\s+(\d+)/)?.[1] ?? 0) * 1024;
|
||||
memResult.used = memResult.total - memResult.available;
|
||||
} catch (e) {
|
||||
this.logger.error(`Unable to read /mnt/host/proc/meminfo: ${e}`);
|
||||
this.logger.error(`Unable to read /host/proc/meminfo: ${e}`);
|
||||
}
|
||||
|
||||
const [disk0] = await si.fsSize();
|
||||
|
@ -28,7 +28,7 @@ export const createAppConfig = (props?: Partial<AppInfo>, isInstalled = true) =>
|
||||
mockFiles[`${DATA_DIR}/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfoSchema.parse(appInfo));
|
||||
mockFiles[`${DATA_DIR}/apps/${appInfo.id}/docker-compose.yml`] = 'compose';
|
||||
mockFiles[`${DATA_DIR}/apps/${appInfo.id}/metadata/description.md`] = 'md desc';
|
||||
mockFiles[`${APP_DATA_DIR}/app-data/${appInfo.id}/data/test.txt`] = 'data';
|
||||
mockFiles[`${APP_DATA_DIR}/${appInfo.id}/data/test.txt`] = 'data';
|
||||
}
|
||||
|
||||
// @ts-expect-error - custom mock method
|
||||
|
@ -16,7 +16,7 @@ export type SettingsFormValues = {
|
||||
internalIp?: string;
|
||||
appsRepoUrl?: string;
|
||||
domain?: string;
|
||||
appDataDirPath?: string;
|
||||
storagePath?: string;
|
||||
localDomain?: string;
|
||||
guestDashboard?: boolean;
|
||||
allowAutoThemes?: boolean;
|
||||
@ -246,7 +246,7 @@ export const SettingsForm = (props: IProps) => {
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Input
|
||||
{...register('appDataDirPath')}
|
||||
{...register('storagePath')}
|
||||
label={
|
||||
<>
|
||||
{t('SETTINGS_GENERAL_STORAGE_PATH')}
|
||||
@ -256,7 +256,7 @@ export const SettingsForm = (props: IProps) => {
|
||||
<span className={clsx('ms-1 form-help storage-path-hint')}>?</span>
|
||||
</>
|
||||
}
|
||||
error={errors.appDataDirPath?.message}
|
||||
error={errors.storagePath?.message}
|
||||
placeholder={t('SETTINGS_GENERAL_STORAGE_PATH')}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,14 +1,14 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import { getUserFromCookie } from '@/server/common/session.helpers';
|
||||
import fs from 'fs-extra';
|
||||
import { APP_DIR } from 'src/config';
|
||||
import { DATA_DIR } from '../../../config/constants';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const user = await getUserFromCookie();
|
||||
|
||||
if (user?.operator) {
|
||||
const filePath = `${APP_DIR}/traefik/tls/cert.pem`;
|
||||
const filePath = `${DATA_DIR}/traefik/tls/cert.pem`;
|
||||
|
||||
if (await fs.pathExists(filePath)) {
|
||||
const file = await fs.promises.readFile(filePath);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { DATA_DIR } from 'src/config';
|
||||
import { createLogger, format, transports } from 'winston';
|
||||
import { DATA_DIR } from '../../../config/constants';
|
||||
|
||||
const { align, printf, timestamp, combine, colorize } = format;
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
import { TipiConfigClass } from './TipiConfig';
|
||||
import { readJsonFile } from '../../common/fs.helpers';
|
||||
|
||||
@ -13,7 +15,6 @@ describe('Test: getConfig', () => {
|
||||
// assert
|
||||
expect(config).toBeDefined();
|
||||
expect(config.NODE_ENV).toBe('test');
|
||||
expect(config.rootFolder).toBe('/runtipi');
|
||||
expect(config.internalIp).toBe('localhost');
|
||||
});
|
||||
|
||||
@ -78,7 +79,7 @@ describe('Test: setConfig', () => {
|
||||
expect(config).toBeDefined();
|
||||
expect(config.appsRepoUrl).toBe(randomWord);
|
||||
|
||||
const settingsJson = readJsonFile('/data/state/settings.json') as { [key: string]: string };
|
||||
const settingsJson = readJsonFile(path.join(DATA_DIR, 'state', 'settings.json')) as { [key: string]: string };
|
||||
|
||||
expect(settingsJson).toBeDefined();
|
||||
expect(settingsJson.appsRepoUrl).toBe(randomWord);
|
||||
|
@ -3,6 +3,8 @@ import { fromAny } from '@total-typescript/shoehorn';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { TestDatabase, clearDatabase, closeDatabase, createDatabase } from '@/server/tests/test-utils';
|
||||
import { appInfoSchema } from '@runtipi/shared';
|
||||
import path from 'path';
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
import { TipiConfig } from '../../core/TipiConfig';
|
||||
import { checkAppRequirements, getAppInfo, getAvailableApps, getUpdateInfo } from './apps.helpers';
|
||||
import { createAppConfig, insertApp } from '../../tests/apps.factory';
|
||||
@ -104,7 +106,7 @@ describe('Test: getAvailableApps()', () => {
|
||||
// arrange
|
||||
const appConfig = createAppConfig();
|
||||
createAppConfig();
|
||||
fs.writeFileSync(`/runtipi/repos/repo-id/apps/${appConfig.id}/config.json`, 'invalid json');
|
||||
fs.writeFileSync(path.join(DATA_DIR, 'repos', 'repo-id', 'apps', appConfig.id, 'config.json'), 'invalid json');
|
||||
|
||||
// act
|
||||
const availableApps = await getAvailableApps();
|
||||
@ -131,7 +133,7 @@ describe('Test: getAppInfo()', () => {
|
||||
const appConfig = createAppConfig();
|
||||
const secondAppConfig = createAppConfig();
|
||||
const app = await insertApp({}, appConfig, db);
|
||||
fs.writeFileSync(`/runtipi/apps/${app.id}/config.json`, JSON.stringify(secondAppConfig));
|
||||
fs.writeFileSync(path.join(DATA_DIR, 'apps', app.id, 'config.json'), JSON.stringify(secondAppConfig));
|
||||
|
||||
// act
|
||||
const result = getAppInfo(app.id, app.status);
|
||||
@ -145,7 +147,7 @@ describe('Test: getAppInfo()', () => {
|
||||
const appConfig = createAppConfig();
|
||||
const app = await insertApp({ status: 'missing' }, appConfig, db);
|
||||
const secondAppConfig = createAppConfig();
|
||||
fs.writeFileSync(`/runtipi/repos/repo-id/apps/${app.id}/config.json`, JSON.stringify(secondAppConfig));
|
||||
fs.writeFileSync(path.join(DATA_DIR, 'repos', 'repo-id', 'apps', app.id, 'config.json'), JSON.stringify(secondAppConfig));
|
||||
|
||||
// act
|
||||
const result = getAppInfo(app.id, app.status);
|
||||
@ -199,7 +201,7 @@ describe('Test: getUpdateInfo()', () => {
|
||||
it('Should return default values if config.json is invalid', async () => {
|
||||
// arrange
|
||||
const appConfig = createAppConfig();
|
||||
fs.writeFileSync(`/runtipi/repos/repo-id/apps/${appConfig.id}/config.json`, 'invalid json');
|
||||
fs.writeFileSync(path.join(DATA_DIR, 'repos', 'repo-id', 'apps', appConfig.id, 'config.json'), 'invalid json');
|
||||
|
||||
// act
|
||||
const updateInfo = getUpdateInfo(appConfig.id);
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { App } from '@/server/db/schema';
|
||||
import { appInfoSchema } from '@runtipi/shared';
|
||||
import { appInfoSchema, sanitizePath } from '@runtipi/shared';
|
||||
import { pathExists } from '@runtipi/shared/node';
|
||||
import { DATA_DIR } from '../../../config';
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
import path from 'path';
|
||||
import { fileExists, readdirSync, readFile, readJsonFile } from '../../common/fs.helpers';
|
||||
import { TipiConfig } from '../../core/TipiConfig';
|
||||
import { Logger } from '../../core/Logger';
|
||||
@ -18,14 +19,15 @@ import { notEmpty } from '../../common/typescript.helpers';
|
||||
* @throws Will throw an error if the app has an invalid config.json file or if the current system architecture is not supported by the app.
|
||||
*/
|
||||
export const checkAppRequirements = (appName: string) => {
|
||||
const configFile = readJsonFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${appName}/config.json`);
|
||||
const { appsRepoId, architecture } = TipiConfig.getConfig();
|
||||
const configFile = readJsonFile(path.join(DATA_DIR, 'repos', sanitizePath(appsRepoId), 'apps', sanitizePath(appName), 'config.json'));
|
||||
const parsedConfig = appInfoSchema.safeParse(configFile);
|
||||
|
||||
if (!parsedConfig.success) {
|
||||
throw new Error(`App ${appName} has invalid config.json file`);
|
||||
}
|
||||
|
||||
if (parsedConfig.data.supported_architectures && !parsedConfig.data.supported_architectures.includes(TipiConfig.getConfig().architecture)) {
|
||||
if (parsedConfig.data.supported_architectures && !parsedConfig.data.supported_architectures.includes(architecture)) {
|
||||
throw new Error(`App ${appName} is not supported on this architecture`);
|
||||
}
|
||||
|
||||
@ -38,12 +40,13 @@ export const checkAppRequirements = (appName: string) => {
|
||||
If the config.json file is invalid, it logs an error message.
|
||||
*/
|
||||
export const getAvailableApps = async () => {
|
||||
if (!(await pathExists(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps`))) {
|
||||
Logger.error(`Apps repo ${TipiConfig.getConfig().appsRepoId} not found. Make sure your repo is configured correctly.`);
|
||||
const { appsRepoId } = TipiConfig.getConfig();
|
||||
if (!(await pathExists(path.join(DATA_DIR, 'repos', sanitizePath(appsRepoId), 'apps')))) {
|
||||
Logger.error(`Apps repo ${appsRepoId} not found. Make sure your repo is configured correctly.`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const appsDir = readdirSync(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps`);
|
||||
const appsDir = readdirSync(path.join(DATA_DIR, 'repos', sanitizePath(appsRepoId), 'apps'));
|
||||
|
||||
const skippedFiles = ['__tests__', 'docker-compose.common.yml', 'schema.json', '.DS_Store'];
|
||||
|
||||
@ -51,14 +54,15 @@ export const getAvailableApps = async () => {
|
||||
.map((app) => {
|
||||
if (skippedFiles.includes(app)) return null;
|
||||
|
||||
const configFile = readJsonFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${app}/config.json`);
|
||||
const repoPath = path.join(DATA_DIR, 'repos', sanitizePath(appsRepoId), 'apps', sanitizePath(app));
|
||||
const configFile = readJsonFile(path.join(repoPath, 'config.json'));
|
||||
const parsedConfig = appInfoSchema.safeParse(configFile);
|
||||
|
||||
if (!parsedConfig.success) {
|
||||
Logger.error(`App ${JSON.stringify(app)} has invalid config.json`);
|
||||
Logger.error(JSON.stringify(parsedConfig.error, null, 2));
|
||||
} else if (parsedConfig.data.available) {
|
||||
const description = readFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${parsedConfig.data.id}/metadata/description.md`);
|
||||
const description = readFile(path.join(repoPath, 'metadata', 'description.md'));
|
||||
return { ...parsedConfig.data, description };
|
||||
}
|
||||
|
||||
@ -78,7 +82,8 @@ export const getAvailableApps = async () => {
|
||||
* @param {string} id - The app id.
|
||||
*/
|
||||
export const getUpdateInfo = (id: string) => {
|
||||
const repoConfig = readJsonFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
const { appsRepoId } = TipiConfig.getConfig();
|
||||
const repoConfig = readJsonFile(path.join(DATA_DIR, 'repos', sanitizePath(appsRepoId), 'apps', sanitizePath(id), 'config.json'));
|
||||
const parsedConfig = appInfoSchema.safeParse(repoConfig);
|
||||
|
||||
if (parsedConfig.success) {
|
||||
@ -106,22 +111,27 @@ export const getAppInfo = (id: string, status?: App['status']) => {
|
||||
// Check if app is installed
|
||||
const installed = typeof status !== 'undefined' && status !== 'missing';
|
||||
|
||||
if (installed && fileExists(`${DATA_DIR}/apps/${id}/config.json`)) {
|
||||
const configFile = readJsonFile(`${DATA_DIR}/apps/${id}/config.json`);
|
||||
const appsFolder = path.join(DATA_DIR, 'apps', sanitizePath(id));
|
||||
|
||||
if (installed && fileExists(path.join(appsFolder, 'config.json'))) {
|
||||
const configFile = readJsonFile(path.join(appsFolder, 'config.json'));
|
||||
const parsedConfig = appInfoSchema.safeParse(configFile);
|
||||
|
||||
if (parsedConfig.success && parsedConfig.data.available) {
|
||||
const description = readFile(`${DATA_DIR}/apps/${id}/metadata/description.md`);
|
||||
const description = readFile(path.join(appsFolder, 'metadata', 'description.md'));
|
||||
return { ...parsedConfig.data, description };
|
||||
}
|
||||
}
|
||||
|
||||
if (fileExists(`/data/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/config.json`)) {
|
||||
const configFile = readJsonFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
const { appsRepoId } = TipiConfig.getConfig();
|
||||
const repoFolder = path.join(DATA_DIR, 'repos', sanitizePath(appsRepoId), 'apps', sanitizePath(id));
|
||||
|
||||
if (fileExists(path.join(repoFolder, 'config.json'))) {
|
||||
const configFile = readJsonFile(path.join(repoFolder, 'config.json'));
|
||||
const parsedConfig = appInfoSchema.safeParse(configFile);
|
||||
|
||||
if (parsedConfig.success && parsedConfig.data.available) {
|
||||
const description = readFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/metadata/description.md`);
|
||||
const description = readFile(path.join(repoFolder, 'metadata', 'description.md'));
|
||||
return { ...parsedConfig.data, description };
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ import waitForExpect from 'wait-for-expect';
|
||||
import { TestDatabase, clearDatabase, closeDatabase, createDatabase } from '@/server/tests/test-utils';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { castAppConfig } from '@/lib/helpers/castAppConfig';
|
||||
import path from 'path';
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
import { AppServiceClass } from './apps.service';
|
||||
import { EventDispatcher } from '../../core/EventDispatcher';
|
||||
import { getAllApps, getAppById, updateApp, createAppConfig, insertApp } from '../../tests/apps.factory';
|
||||
@ -116,7 +118,7 @@ describe('Install app', () => {
|
||||
it('Should throw if config.json is not valid', async () => {
|
||||
// arrange
|
||||
const appConfig = createAppConfig({});
|
||||
fs.writeFileSync(`/runtipi/repos/repo-id/apps/${appConfig.id}/config.json`, 'test');
|
||||
fs.writeFileSync(path.join(DATA_DIR, 'repos', 'repo-id', 'apps', appConfig.id, 'config.json'), 'test');
|
||||
|
||||
// act & assert
|
||||
await expect(AppsService.installApp(appConfig.id, {})).rejects.toThrowError(`App ${appConfig.id} has invalid config.json file`);
|
||||
@ -317,7 +319,7 @@ describe('List apps', () => {
|
||||
// arrange
|
||||
const appInfo = createAppConfig({});
|
||||
createAppConfig({});
|
||||
fs.writeFileSync(`/runtipi/repos/repo-id/apps/${appInfo.id}/config.json`, 'invalid json');
|
||||
fs.writeFileSync(path.join(DATA_DIR, 'repos', 'repo-id', 'apps', appInfo.id, 'config.json'), 'invalid json');
|
||||
|
||||
// act
|
||||
const { apps } = await AppsService.listApps();
|
||||
@ -373,8 +375,8 @@ describe('installedApps', () => {
|
||||
await insertApp({}, appConfig2, db);
|
||||
await insertApp({}, appConfig3, db);
|
||||
|
||||
fs.writeFileSync(`/runtipi/apps/${appConfig3.id}/config.json`, 'invalid json');
|
||||
fs.writeFileSync(`/runtipi/repos/repo-id/apps/${appConfig3.id}/config.json`, 'invalid json');
|
||||
fs.writeFileSync(path.join(DATA_DIR, 'apps', appConfig3.id, 'config.json'), 'invalid json');
|
||||
fs.writeFileSync(path.join(DATA_DIR, 'repos', 'repo-id', 'apps', appConfig3.id, 'config.json'), 'invalid json');
|
||||
|
||||
// act
|
||||
const apps = await AppsService.installedApps();
|
||||
|
@ -8,6 +8,8 @@ import { mockInsert, mockQuery, mockSelect } from '@/tests/mocks/drizzle';
|
||||
import { createDatabase, clearDatabase, closeDatabase, TestDatabase } from '@/server/tests/test-utils';
|
||||
import { v4 } from 'uuid';
|
||||
import { tipiCache } from '@/server/core/TipiCache';
|
||||
import path from 'path';
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
import { encrypt } from '../../utils/encryption';
|
||||
import { TipiConfig } from '../../core/TipiConfig';
|
||||
import { createUser, getUserByEmail, getUserById } from '../../tests/user.factory';
|
||||
@ -538,7 +540,7 @@ describe('Test: changeOperatorPassword', () => {
|
||||
const user = await createUser({ email }, database);
|
||||
const newPassword = faker.internet.password();
|
||||
// @ts-expect-error - mocking fs
|
||||
fs.__createMockFiles({ '/runtipi/state/password-change-request': '' });
|
||||
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: '' });
|
||||
|
||||
// Act
|
||||
const result = await AuthService.changeOperatorPassword({ newPassword });
|
||||
@ -567,7 +569,7 @@ describe('Test: changeOperatorPassword', () => {
|
||||
await createUser({ email, operator: false }, database);
|
||||
const newPassword = faker.internet.password();
|
||||
// @ts-expect-error - mocking fs
|
||||
fs.__createMockFiles({ '/runtipi/state/password-change-request': '' });
|
||||
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: '' });
|
||||
|
||||
// Act & Assert
|
||||
await expect(AuthService.changeOperatorPassword({ newPassword })).rejects.toThrowError('AUTH_ERROR_OPERATOR_NOT_FOUND');
|
||||
@ -579,7 +581,7 @@ describe('Test: changeOperatorPassword', () => {
|
||||
const user = await createUser({ email, totpEnabled: true }, database);
|
||||
const newPassword = faker.internet.password();
|
||||
// @ts-expect-error - mocking fs
|
||||
fs.__createMockFiles({ '/runtipi/state/password-change-request': '' });
|
||||
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: '' });
|
||||
|
||||
// Act
|
||||
const result = await AuthService.changeOperatorPassword({ newPassword });
|
||||
@ -597,7 +599,7 @@ describe('Test: checkPasswordChangeRequest', () => {
|
||||
it('should return true if the password change request file exists', async () => {
|
||||
// Arrange
|
||||
// @ts-expect-error - mocking fs
|
||||
fs.__createMockFiles({ '/runtipi/state/password-change-request': '' });
|
||||
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: '' });
|
||||
|
||||
// Act
|
||||
const result = AuthServiceClass.checkPasswordChangeRequest();
|
||||
@ -623,13 +625,13 @@ describe('Test: cancelPasswordChangeRequest', () => {
|
||||
it('should delete the password change request file', async () => {
|
||||
// Arrange
|
||||
// @ts-expect-error - mocking fs
|
||||
fs.__createMockFiles({ '/runtipi/state/password-change-request': '' });
|
||||
fs.__createMockFiles({ [path.join(DATA_DIR, 'state', 'password-change-request')]: '' });
|
||||
|
||||
// Act
|
||||
await AuthServiceClass.cancelPasswordChangeRequest();
|
||||
|
||||
// Assert
|
||||
expect(fs.existsSync('/runtipi/state/password-change-request')).toBe(false);
|
||||
expect(fs.existsSync(path.join(DATA_DIR, 'state', 'password-change-request'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { Locales, getLocaleFromString } from '@/shared/internationalization/loca
|
||||
import { generateSessionId, setSession } from '@/server/common/session.helpers';
|
||||
import { Database } from '@/server/db';
|
||||
import { tipiCache } from '@/server/core/TipiCache/TipiCache';
|
||||
import { DATA_DIR } from '../../../config';
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
import { TipiConfig } from '../../core/TipiConfig';
|
||||
import { fileExists, unlinkFile } from '../../common/fs.helpers';
|
||||
import { decrypt, encrypt } from '../../utils/encryption';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { promises } from 'fs';
|
||||
import axios from 'redaxios';
|
||||
import { tipiCache } from '@/server/core/TipiCache';
|
||||
import { DATA_DIR } from '../../../config';
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
import { fileExists } from '../../common/fs.helpers';
|
||||
import { Logger } from '../../core/Logger';
|
||||
import { TipiConfig } from '../../core/TipiConfig';
|
||||
|
@ -35,10 +35,10 @@ const createAppConfig = (props?: Partial<AppInfo>) => {
|
||||
});
|
||||
|
||||
const mockFiles: Record<string, string | string[]> = {};
|
||||
mockFiles['/data/.env'] = 'TEST=test';
|
||||
mockFiles[`/data/repos/repo-id/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfoSchema.parse(appInfo));
|
||||
mockFiles[`/data/repos/repo-id/apps/${appInfo.id}/docker-compose.yml`] = 'compose';
|
||||
mockFiles[`/data/repos/repo-id/apps/${appInfo.id}/metadata/description.md`] = 'md desc';
|
||||
mockFiles[`${DATA_DIR}/.env`] = 'TEST=test';
|
||||
mockFiles[`${DATA_DIR}/repos/repo-id/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfoSchema.parse(appInfo));
|
||||
mockFiles[`${DATA_DIR}/repos/repo-id/apps/${appInfo.id}/docker-compose.yml`] = 'compose';
|
||||
mockFiles[`${DATA_DIR}/repos/repo-id/apps/${appInfo.id}/metadata/description.md`] = 'md desc';
|
||||
|
||||
// @ts-expect-error - custom mock method
|
||||
fs.__applyMockFiles(mockFiles);
|
||||
@ -147,7 +147,7 @@ const insertApp = async (data: Partial<NewApp>, appInfo: AppInfo, database: Test
|
||||
|
||||
const mockFiles: Record<string, string | string[]> = {};
|
||||
if (data.status !== 'missing') {
|
||||
mockFiles[`app-data/${values.id}/app.env`] = `TEST=test\nAPP_PORT=3000\n${Object.entries(data.config || {})
|
||||
mockFiles[`${APP_DATA_DIR}/${values.id}/app.env`] = `TEST=test\nAPP_PORT=3000\n${Object.entries(data.config || {})
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join('\n')}`;
|
||||
mockFiles[`${DATA_DIR}/apps/${values.id}/config.json`] = JSON.stringify(appInfo);
|
||||
|
@ -2,6 +2,8 @@ import fs from 'fs-extra';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { Job } from 'bullmq';
|
||||
import { tipiCache } from '@/server/core/TipiCache';
|
||||
import path from 'path';
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
|
||||
global.fetch = jest.fn();
|
||||
// Mock global location
|
||||
@ -40,9 +42,9 @@ console.error = jest.fn();
|
||||
beforeEach(async () => {
|
||||
// @ts-expect-error - custom mock method
|
||||
fs.__resetAllMocks();
|
||||
await fs.promises.mkdir('/runtipi/state', { recursive: true });
|
||||
await fs.promises.writeFile('/runtipi/state/settings.json', '{}');
|
||||
await fs.promises.mkdir('/app/logs', { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'state'), { recursive: true });
|
||||
await fs.promises.writeFile(path.join(DATA_DIR, 'state', 'settings.json'), '{}');
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'logs'), { recursive: true });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -33,6 +33,9 @@
|
||||
"@/api/*": [
|
||||
"./src/app/api/*"
|
||||
],
|
||||
"@/config/*": [
|
||||
"./src/config/*"
|
||||
],
|
||||
},
|
||||
"lib": [
|
||||
"dom",
|
||||
|
Loading…
Reference in New Issue
Block a user