test(server): avoid progress get hold after tests finished (#5522)

This commit is contained in:
liuyi 2024-01-11 06:40:53 +00:00
parent 5aee480c50
commit 9253e522aa
No known key found for this signature in database
GPG Key ID: 56709255DC7EC728
36 changed files with 262 additions and 194 deletions

View File

@ -5,21 +5,27 @@ const env = process.env;
const node = AFFiNE.node;
// TODO: may be separate config overring in `affine.[env].config`?
if (node.prod && env.R2_OBJECT_STORAGE_ACCOUNT_ID) {
AFFiNE.storage.providers.r2 = {
accountId: env.R2_OBJECT_STORAGE_ACCOUNT_ID,
credentials: {
accessKeyId: env.R2_OBJECT_STORAGE_ACCESS_KEY_ID!,
secretAccessKey: env.R2_OBJECT_STORAGE_SECRET_ACCESS_KEY!,
},
};
AFFiNE.storage.storages.avatar.provider = 'r2';
AFFiNE.storage.storages.avatar.bucket = 'account-avatar';
AFFiNE.storage.storages.avatar.publicLinkFactory = key =>
`https://avatar.affineassets.com/${key}`;
if (node.prod) {
// Storage
if (env.R2_OBJECT_STORAGE_ACCOUNT_ID) {
AFFiNE.storage.providers.r2 = {
accountId: env.R2_OBJECT_STORAGE_ACCOUNT_ID,
credentials: {
accessKeyId: env.R2_OBJECT_STORAGE_ACCESS_KEY_ID!,
secretAccessKey: env.R2_OBJECT_STORAGE_SECRET_ACCESS_KEY!,
},
};
AFFiNE.storage.storages.avatar.provider = 'r2';
AFFiNE.storage.storages.avatar.bucket = 'account-avatar';
AFFiNE.storage.storages.avatar.publicLinkFactory = key =>
`https://avatar.affineassets.com/${key}`;
AFFiNE.storage.storages.blob.provider = 'r2';
AFFiNE.storage.storages.blob.bucket = `workspace-blobs-${
AFFiNE.affine.canary ? 'canary' : 'prod'
}`;
AFFiNE.storage.storages.blob.provider = 'r2';
AFFiNE.storage.storages.blob.bucket = `workspace-blobs-${
AFFiNE.affine.canary ? 'canary' : 'prod'
}`;
}
// Metrics
AFFiNE.metrics.enabled = true;
}

View File

@ -0,0 +1,12 @@
import { homedir } from 'node:os';
import { join } from 'node:path';
import { config } from 'dotenv';
import { SERVER_FLAVOR } from './config/default';
if (SERVER_FLAVOR === 'selfhosted') {
config({ path: join(homedir(), '.affine', '.env') });
} else {
config();
}

View File

@ -3,24 +3,32 @@ import { APP_INTERCEPTOR } from '@nestjs/core';
import { AppController } from './app.controller';
import { CacheInterceptor, CacheModule } from './cache';
import { RedisModule } from './cache/redis';
import { ConfigModule, SERVER_FLAVOR } from './config';
import { EventModule } from './event';
import { MetricsModule } from './metrics';
import { BusinessModules } from './modules';
import { AuthModule } from './modules/auth';
import { PrismaModule } from './prisma';
import { SessionModule } from './session';
import { RateLimiterModule } from './throttler';
const BasicModules = [
PrismaModule,
export const BasicModules = [
ConfigModule.forRoot(),
CacheModule,
PrismaModule,
MetricsModule,
EventModule,
SessionModule,
RateLimiterModule,
AuthModule,
];
// better module registration logic
if (AFFiNE.redis.enabled) {
BasicModules.push(RedisModule);
}
@Module({
providers: [
{

View File

@ -1,26 +1,34 @@
import { FactoryProvider, Global, Module } from '@nestjs/common';
import { Global, Module, Provider, Type } from '@nestjs/common';
import { Redis } from 'ioredis';
import { Config } from '../config';
import { LocalCache } from './cache';
import { RedisCache } from './redis';
import { SessionCache, ThrottlerCache } from './instances';
import { LocalCache } from './providers/cache';
import { RedisCache } from './providers/redis';
import { CacheRedis, SessionRedis, ThrottlerRedis } from './redis';
const CacheProvider: FactoryProvider = {
provide: LocalCache,
useFactory: (config: Config) => {
return config.redis.enabled
? new RedisCache(new Redis(config.redis))
: new LocalCache();
},
inject: [Config],
};
function makeCacheProvider(CacheToken: Type, RedisToken: Type): Provider {
return {
provide: CacheToken,
useFactory: (redis?: Redis) => {
return redis ? new RedisCache(redis) : new LocalCache();
},
inject: [{ token: RedisToken, optional: true }],
};
}
const CacheProvider = makeCacheProvider(LocalCache, CacheRedis);
const SessionCacheProvider = makeCacheProvider(SessionCache, SessionRedis);
const ThrottlerCacheProvider = makeCacheProvider(
ThrottlerCache,
ThrottlerRedis
);
@Global()
@Module({
providers: [CacheProvider],
exports: [CacheProvider],
providers: [CacheProvider, SessionCacheProvider, ThrottlerCacheProvider],
exports: [CacheProvider, SessionCacheProvider, ThrottlerCacheProvider],
})
export class CacheModule {}
export { LocalCache as Cache };
export { LocalCache as Cache, SessionCache, ThrottlerCache };
export { CacheInterceptor, MakeCache, PreventCache } from './interceptor';

View File

@ -0,0 +1,4 @@
import { LocalCache } from './providers/cache';
export class SessionCache extends LocalCache {}
export class ThrottlerCache extends LocalCache {}

View File

@ -10,7 +10,7 @@ import { Reflector } from '@nestjs/core';
import { GqlContextType, GqlExecutionContext } from '@nestjs/graphql';
import { mergeMap, Observable, of } from 'rxjs';
import { LocalCache } from './cache';
import { LocalCache } from './providers/cache';
export const MakeCache = (key: string[], args?: string[]) =>
SetMetadata('cacheKey', [key, args]);

View File

@ -1,3 +1,4 @@
import { Injectable } from '@nestjs/common';
import Keyv from 'keyv';
export interface CacheSetOptions {
@ -50,11 +51,12 @@ export interface Cache {
mapLen(map: string): Promise<number>;
}
@Injectable()
export class LocalCache implements Cache {
private readonly kv: Keyv;
constructor() {
this.kv = new Keyv();
constructor(opts: Keyv.Options<any> = {}) {
this.kv = new Keyv(opts);
}
// standard operation

View File

@ -0,0 +1,38 @@
import { Global, Injectable, Module, OnModuleDestroy } from '@nestjs/common';
import { Redis as IORedis } from 'ioredis';
import { Config } from '../../config';
class Redis extends IORedis implements OnModuleDestroy {
onModuleDestroy() {
this.disconnect();
}
}
@Injectable()
export class CacheRedis extends Redis {
constructor(config: Config) {
super({ ...config.redis, db: config.redis.database });
}
}
@Injectable()
export class ThrottlerRedis extends Redis {
constructor(config: Config) {
super({ ...config.redis, db: config.redis.database + 1 });
}
}
@Injectable()
export class SessionRedis extends Redis {
constructor(config: Config) {
super({ ...config.redis, db: config.redis.database + 2 });
}
}
@Global()
@Module({
providers: [CacheRedis, ThrottlerRedis, SessionRedis],
exports: [CacheRedis, ThrottlerRedis, SessionRedis],
})
export class RedisModule {}

View File

@ -357,6 +357,10 @@ export interface AFFiNEConfig {
};
};
metrics: {
enabled: boolean;
};
payment: {
stripe: {
keys: {

View File

@ -211,6 +211,9 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
apiVersion: '2023-10-16',
},
},
metrics: {
enabled: false,
},
} satisfies AFFiNEConfig;
applyEnvToConfig(defaultConfig);

View File

@ -5,8 +5,6 @@ import { merge } from 'lodash-es';
import type { DeepPartial } from '../utils/types';
import type { AFFiNEConfig } from './def';
import '../prelude';
type ConstructorOf<T> = {
new (): T;
};

View File

@ -1,3 +1,5 @@
import '../prelude';
import { Logger, Module } from '@nestjs/common';
import { CommandFactory } from 'nest-commander';
@ -14,6 +16,9 @@ import { RevertCommand, RunCommand } from './commands/run';
enableUpdateAutoMerging: false,
},
},
metrics: {
enabled: false,
},
}),
BusinessAppModule,
],

View File

@ -59,13 +59,13 @@ export class CreateCommand extends CommandRunner {
}
private createScript(name: string) {
const contents = ["import { PrismaService } from '../../prisma';", ''];
const contents = ["import { PrismaClient } from '@prisma/client';", ''];
contents.push(`export class ${name} {`);
contents.push(' // do the migration');
contents.push(' static async up(db: PrismaService) {}');
contents.push(' static async up(db: PrismaClient) {}');
contents.push('');
contents.push(' // revert the migration');
contents.push(' static async down(db: PrismaService) {}');
contents.push(' static async down(db: PrismaClient) {}');
contents.push('}');

View File

@ -1,11 +1,11 @@
import { PrismaClient } from '@prisma/client';
import { applyUpdate, Doc, encodeStateAsUpdate } from 'yjs';
import { PrismaService } from '../../prisma';
import { DocID } from '../../utils/doc';
export class Guid1698398506533 {
// do the migration
static async up(db: PrismaService) {
static async up(db: PrismaClient) {
let turn = 0;
let lastTurnCount = 100;
while (lastTurnCount === 100) {

View File

@ -1,11 +1,12 @@
import { PrismaClient } from '@prisma/client';
import { Features } from '../../modules/features';
import { Quotas } from '../../modules/quota/schema';
import { PrismaService } from '../../prisma';
import { migrateNewFeatureTable, upsertFeature } from './utils/user-features';
export class UserFeaturesInit1698652531198 {
// do the migration
static async up(db: PrismaService) {
static async up(db: PrismaClient) {
// upgrade features from lower version to higher version
for (const feature of Features) {
await upsertFeature(db, feature);
@ -18,7 +19,7 @@ export class UserFeaturesInit1698652531198 {
}
// revert the migration
static async down(_db: PrismaService) {
static async down(_db: PrismaClient) {
// TODO: revert the migration
}
}

View File

@ -1,8 +1,7 @@
import { PrismaService } from '../../prisma';
import { PrismaClient } from '@prisma/client';
export class PagePermission1699005339766 {
// do the migration
static async up(db: PrismaService) {
static async up(db: PrismaClient) {
let turn = 0;
let lastTurnCount = 50;
const done = new Set<string>();
@ -88,7 +87,7 @@ export class PagePermission1699005339766 {
}
// revert the migration
static async down(db: PrismaService) {
static async down(db: PrismaClient) {
await db.workspaceUserPermission.deleteMany({});
await db.workspacePageUserPermission.deleteMany({});
}

View File

@ -1,9 +1,9 @@
import { QuotaType } from '../../modules/quota/types';
import { PrismaService } from '../../prisma';
import { PrismaClient } from '@prisma/client';
import { QuotaType } from '../../modules/quota/types';
export class OldUserFeature1702620653283 {
// do the migration
static async up(db: PrismaService) {
static async up(db: PrismaClient) {
await db.$transaction(async tx => {
const latestFreePlan = await tx.features.findFirstOrThrow({
where: { feature: QuotaType.FreePlanV1 },
@ -31,7 +31,7 @@ export class OldUserFeature1702620653283 {
// revert the migration
// WARN: this will drop all user features
static async down(db: PrismaService) {
static async down(db: PrismaClient) {
await db.userFeatures.deleteMany({});
}
}

View File

@ -1,9 +1,10 @@
import { PrismaClient } from '@prisma/client';
import type { UserType } from '../../modules/users';
import { PrismaService } from '../../prisma';
export class UnamedAccount1703756315970 {
// do the migration
static async up(db: PrismaService) {
static async up(db: PrismaClient) {
await db.$transaction(async tx => {
// only find users with empty names
const users = await db.$queryRaw<
@ -27,5 +28,5 @@ export class UnamedAccount1703756315970 {
}
// revert the migration
static async down(_db: PrismaService) {}
static async down(_db: PrismaClient) {}
}

View File

@ -1,11 +1,11 @@
import { ModuleRef } from '@nestjs/core';
import { PrismaClient } from '@prisma/client';
import { WorkspaceBlobStorage } from '../../modules/storage';
import { PrismaService } from '../../prisma';
export class WorkspaceBlobs1703828796699 {
// do the migration
static async up(db: PrismaService, injector: ModuleRef) {
static async up(db: PrismaClient, injector: ModuleRef) {
const blobStorage = injector.get(WorkspaceBlobStorage, { strict: false });
let hasMore = true;
let turn = 0;
@ -32,7 +32,7 @@ export class WorkspaceBlobs1703828796699 {
}
// revert the migration
static async down(_db: PrismaService) {
static async down(_db: PrismaClient) {
// old data kept, no need to downgrade the migration
}
}

View File

@ -1,10 +1,11 @@
import { PrismaClient } from '@prisma/client';
import { Features } from '../../modules/features';
import { PrismaService } from '../../prisma';
import { upsertFeature } from './utils/user-features';
export class RefreshUserFeatures1704352562369 {
// do the migration
static async up(db: PrismaService) {
static async up(db: PrismaClient) {
// add early access v2 & copilot feature
for (const feature of Features) {
await upsertFeature(db, feature);
@ -12,5 +13,5 @@ export class RefreshUserFeatures1704352562369 {
}
// revert the migration
static async down(_db: PrismaService) {}
static async down(_db: PrismaClient) {}
}

View File

@ -1,15 +1,14 @@
import { Prisma } from '@prisma/client';
import { Prisma, PrismaClient } from '@prisma/client';
import {
CommonFeature,
FeatureKind,
FeatureType,
} from '../../../modules/features';
import { PrismaService } from '../../../prisma';
// upgrade features from lower version to higher version
export async function upsertFeature(
db: PrismaService,
db: PrismaClient,
feature: CommonFeature
): Promise<void> {
const hasEqualOrGreaterVersion =
@ -34,7 +33,7 @@ export async function upsertFeature(
}
}
export async function migrateNewFeatureTable(prisma: PrismaService) {
export async function migrateNewFeatureTable(prisma: PrismaClient) {
const waitingList = await prisma.newFeaturesWaitingList.findMany();
for (const oldUser of waitingList) {
const user = await prisma.user.findFirst({

View File

@ -1,6 +1,7 @@
/// <reference types="./global.d.ts" />
import { start as startAutoMetrics } from './metrics';
startAutoMetrics();
// keep the config import at the top
// eslint-disable-next-line simple-import-sort/imports
import './prelude';
import { NestFactory } from '@nestjs/core';
import type { NestExpressApplication } from '@nestjs/platform-express';
@ -12,6 +13,7 @@ import { Config } from './config';
import { ExceptionLogger } from './middleware/exception-logger';
import { serverTimingAndCache } from './middleware/timing';
import { RedisIoAdapter } from './modules/sync/redis-adapter';
import { CacheRedis } from './cache/redis';
const { NODE_ENV, AFFINE_ENV } = process.env;
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
@ -42,15 +44,10 @@ const config = app.get(Config);
const host = config.node.prod ? '0.0.0.0' : 'localhost';
const port = config.port ?? 3010;
if (config.redis.enabled) {
if (!config.node.test && config.redis.enabled) {
const redis = app.get(CacheRedis, { strict: false });
const redisIoAdapter = new RedisIoAdapter(app);
await redisIoAdapter.connectToRedis(
config.redis.host,
config.redis.port,
config.redis.username,
config.redis.password,
config.redis.database
);
await redisIoAdapter.connectToRedis(redis);
app.useWebSocketAdapter(redisIoAdapter);
}

View File

@ -1,3 +1,33 @@
import { Global, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { Config } from '../config';
import { parseEnvValue } from '../config/def';
import { createSDK, registerCustomMetrics } from './opentelemetry';
@Global()
@Module({})
export class MetricsModule implements OnModuleInit, OnModuleDestroy {
private sdk: NodeSDK | null = null;
constructor(private readonly config: Config) {}
onModuleInit() {
if (
this.config.metrics.enabled &&
!parseEnvValue(process.env.DISABLE_TELEMETRY, 'boolean')
) {
this.sdk = createSDK();
this.sdk.start();
registerCustomMetrics();
}
}
async onModuleDestroy() {
if (this.config.metrics.enabled && this.sdk) {
await this.sdk.shutdown();
}
}
}
export * from './metrics';
export { start } from './opentelemetry';
export * from './utils';

View File

@ -127,3 +127,5 @@ export const metrics = new Proxy<Record<KnownMetricScopes, ScopedMetrics>>(
},
}
);
export function stopMetrics() {}

View File

@ -33,7 +33,6 @@ import prismaInstrument from '@prisma/instrumentation';
const { PrismaInstrumentation } = prismaInstrument;
import { parseEnvValue } from '../config/def';
import { PrismaMetricProducer } from './prisma';
abstract class OpentelemetryFactor {
@ -116,7 +115,8 @@ class DebugOpentelemetryFactor extends OpentelemetryFactor {
}
}
function createSDK() {
// TODO(@forehalo): make it configurable
export function createSDK() {
let factor: OpentelemetryFactor | null = null;
if (process.env.NODE_ENV === 'production') {
factor = new GCloudOpentelemetryFactor();
@ -129,21 +129,11 @@ function createSDK() {
return factor?.create();
}
let OPENTELEMETRY_STARTED = false;
function ensureStarted() {
if (!OPENTELEMETRY_STARTED) {
OPENTELEMETRY_STARTED = true;
start();
}
}
function getMeterProvider() {
ensureStarted();
return metrics.getMeterProvider();
}
function registerCustomMetrics() {
export function registerCustomMetrics() {
const hostMetricsMonitoring = new HostMetrics({
name: 'instance-host-metrics',
meterProvider: getMeterProvider() as MeterProvider,
@ -154,15 +144,3 @@ function registerCustomMetrics() {
export function getMeter(name = 'business') {
return getMeterProvider().getMeter(name);
}
export function start() {
if (parseEnvValue(process.env.DISABLE_TELEMETRY, 'boolean')) {
return;
}
const sdk = createSDK();
if (sdk) {
sdk.start();
registerCustomMetrics();
}
}

View File

@ -167,8 +167,13 @@ export class AuthResolver {
@CurrentUser() user: UserType,
@Args('token') token: string
) {
const key = await this.session.get(token);
if (!key) {
throw new ForbiddenException('Invalid token');
}
// email has set token in `sendVerifyChangeEmail`
const [id, email] = (await this.session.get(token)).split(',');
const [id, email] = key.split(',');
if (!id || id !== user.id || !email) {
throw new ForbiddenException('Invalid token');
}

View File

@ -6,18 +6,8 @@ import { ServerOptions } from 'socket.io';
export class RedisIoAdapter extends IoAdapter {
private adapterConstructor: ReturnType<typeof createAdapter> | undefined;
async connectToRedis(
host: string,
port: number,
username: string,
password: string,
db: number
): Promise<void> {
const pubClient = new Redis(port, host, {
username,
password,
db,
});
async connectToRedis(redis: Redis): Promise<void> {
const pubClient = redis;
pubClient.on('error', err => {
console.error(err);
});

View File

@ -1,17 +1,5 @@
import { homedir } from 'node:os';
import { join } from 'node:path';
import { config } from 'dotenv';
import { SERVER_FLAVOR } from './config/default';
if (SERVER_FLAVOR === 'selfhosted') {
config({ path: join(homedir(), '.affine', '.env') });
} else {
config();
}
import 'reflect-metadata';
import './affine.env';
import './affine';
import './affine.config';

View File

@ -1,44 +1,13 @@
import KeyvRedis from '@keyv/redis';
import {
FactoryProvider,
Global,
Inject,
Injectable,
Module,
} from '@nestjs/common';
import Redis from 'ioredis';
import Keyv from 'keyv';
import { Global, Injectable, Module } from '@nestjs/common';
import { Config } from './config';
export const KeyvProvide = Symbol('KeyvProvide');
export const KeyvProvider: FactoryProvider<Keyv> = {
provide: KeyvProvide,
useFactory(config: Config) {
if (config.redis.enabled) {
return new Keyv({
store: new KeyvRedis(
new Redis(config.redis.port, config.redis.host, {
username: config.redis.username,
password: config.redis.password,
db: config.redis.database + 2,
})
),
});
} else {
return new Keyv();
}
},
inject: [Config],
};
import { SessionCache } from './cache/instances';
@Injectable()
export class SessionService {
private readonly prefix = 'session:';
private readonly sessionTtl = 30 * 60 * 1000; // 30 min
constructor(@Inject(KeyvProvide) private readonly cache: Keyv) {}
constructor(private readonly cache: SessionCache) {}
/**
* get session
@ -46,7 +15,7 @@ export class SessionService {
* @returns
*/
async get(key: string) {
return this.cache.get(this.prefix + key);
return this.cache.get<string>(this.prefix + key);
}
/**
@ -57,7 +26,9 @@ export class SessionService {
* @returns return true if success
*/
async set(key: string, value?: any, sessionTtl = this.sessionTtl) {
return this.cache.set(this.prefix + key, value, sessionTtl);
return this.cache.set<string>(this.prefix + key, value, {
ttl: sessionTtl,
});
}
async delete(key: string) {
@ -67,7 +38,7 @@ export class SessionService {
@Global()
@Module({
providers: [KeyvProvider, SessionService],
exports: [KeyvProvider, SessionService],
providers: [SessionService],
exports: [SessionService],
})
export class SessionModule {}

View File

@ -5,42 +5,50 @@ import {
ThrottlerGuard,
ThrottlerModule,
ThrottlerModuleOptions,
ThrottlerOptionsFactory,
} from '@nestjs/throttler';
import Redis from 'ioredis';
import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis';
import { ThrottlerCache } from './cache';
import { Config } from './config';
import { getRequestResponseFromContext } from './utils/nestjs';
@Injectable()
class CustomOptionsFactory implements ThrottlerOptionsFactory {
constructor(
private readonly config: Config,
private readonly cache: ThrottlerCache
) {}
createThrottlerOptions() {
const options: ThrottlerModuleOptions = {
throttlers: [
{
ttl: this.config.rateLimiter.ttl,
limit: this.config.rateLimiter.limit,
},
],
skipIf: () => {
return !this.config.node.prod || this.config.affine.canary;
},
};
if (this.config.redis.enabled) {
new Logger(RateLimiterModule.name).log('Use Redis');
options.storage = new ThrottlerStorageRedisService(
// @ts-expect-error hidden field
this.cache.redis
);
}
return options;
}
}
@Global()
@Module({
imports: [
ThrottlerModule.forRootAsync({
inject: [Config],
useFactory: (config: Config): ThrottlerModuleOptions => {
const options: ThrottlerModuleOptions = {
throttlers: [
{
ttl: config.rateLimiter.ttl,
limit: config.rateLimiter.limit,
},
],
skipIf: () => {
return !config.node.prod || config.affine.canary;
},
};
if (config.redis.enabled) {
new Logger(RateLimiterModule.name).log('Use Redis');
options.storage = new ThrottlerStorageRedisService(
new Redis(config.redis.port, config.redis.host, {
username: config.redis.username,
password: config.redis.password,
db: config.redis.database + 1,
})
);
}
return options;
},
useClass: CustomOptionsFactory,
}),
],
})

View File

@ -3,6 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import test from 'ava';
import { CacheModule } from '../src/cache';
import { ConfigModule } from '../src/config';
import { RevertCommand, RunCommand } from '../src/data/commands/run';
import { GqlModule } from '../src/graphql.module';
@ -39,6 +40,7 @@ test.beforeEach(async () => {
https: true,
}),
PrismaModule,
CacheModule,
GqlModule,
AuthModule,
RateLimiterModule,

View File

@ -5,6 +5,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import ava, { type TestFn } from 'ava';
import { CacheModule } from '../src/cache';
import { ConfigModule } from '../src/config';
import { RevertCommand, RunCommand } from '../src/data/commands/run';
import { AuthModule } from '../src/modules/auth';
@ -80,6 +81,7 @@ test.beforeEach(async t => {
},
}),
PrismaModule,
CacheModule,
AuthModule,
FeatureModule,
RateLimiterModule,

View File

@ -10,6 +10,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import ava, { type TestFn } from 'ava';
import { CacheModule } from '../src/cache';
import { ConfigModule } from '../src/config';
import { RevertCommand, RunCommand } from '../src/data/commands/run';
import { GqlModule } from '../src/graphql.module';
@ -45,6 +46,7 @@ test.beforeEach(async t => {
PrismaModule,
GqlModule,
AuthModule,
CacheModule,
RateLimiterModule,
],
providers: [RevertCommand, RunCommand],

View File

@ -4,6 +4,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import ava, { type TestFn } from 'ava';
import { CacheModule } from '../src/cache';
import { ConfigModule } from '../src/config';
import { RevertCommand, RunCommand } from '../src/data/commands/run';
import { EventModule } from '../src/event';
@ -49,6 +50,7 @@ test.beforeEach(async t => {
https: true,
}),
PrismaModule,
CacheModule,
AuthModule,
EventModule,
QuotaModule,

View File

@ -3,6 +3,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import ava, { type TestFn } from 'ava';
import { CacheModule } from '../src/cache';
import { ConfigModule } from '../src/config';
import { SessionModule, SessionService } from '../src/session';
@ -19,6 +20,7 @@ test.beforeEach(async t => {
enabled: false,
},
}),
CacheModule,
SessionModule,
],
}).compile();