summaryrefslogtreecommitdiff
path: root/packages/backend/test/misc
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/test/misc')
-rw-r--r--packages/backend/test/misc/FakeInternalEventService.ts92
-rw-r--r--packages/backend/test/misc/mock-resolver.ts2
-rw-r--r--packages/backend/test/misc/noOpCaches.ts187
3 files changed, 281 insertions, 0 deletions
diff --git a/packages/backend/test/misc/FakeInternalEventService.ts b/packages/backend/test/misc/FakeInternalEventService.ts
new file mode 100644
index 0000000000..d18a080eaf
--- /dev/null
+++ b/packages/backend/test/misc/FakeInternalEventService.ts
@@ -0,0 +1,92 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import type { Listener, ListenerProps } from '@/core/InternalEventService.js';
+import type Redis from 'ioredis';
+import type { GlobalEventService, InternalEventTypes } from '@/core/GlobalEventService.js';
+import { InternalEventService } from '@/core/InternalEventService.js';
+import { bindThis } from '@/decorators.js';
+
+type FakeCall<K extends keyof InternalEventService> = [K, Parameters<InternalEventService[K]>];
+type FakeListener<K extends keyof InternalEventTypes> = [K, Listener<K>, ListenerProps];
+
+/**
+ * Minimal implementation of InternalEventService meant for use in unit tests.
+ * There is no redis connection, and metadata is tracked in the public _calls and _listeners arrays.
+ * The on/off/emit methods are fully functional and can be called in tests to invoke any registered listeners.
+ */
+export class FakeInternalEventService extends InternalEventService {
+ /**
+ * List of calls to public methods, in chronological order.
+ */
+ public _calls: FakeCall<keyof InternalEventService>[] = [];
+
+ /**
+ * List of currently registered listeners.
+ */
+ public _listeners: FakeListener<keyof InternalEventTypes>[] = [];
+
+ /**
+ * Resets the mock.
+ * Clears all listeners and tracked calls.
+ */
+ public _reset() {
+ this._calls = [];
+ this._listeners = [];
+ }
+
+ /**
+ * Simulates a remote event sent from another process in the cluster via redis.
+ */
+ @bindThis
+ public async _emitRedis<K extends keyof InternalEventTypes>(type: K, value: InternalEventTypes[K]): Promise<void> {
+ await this.emit(type, value, false);
+ }
+
+ constructor() {
+ super(
+ { on: () => {} } as unknown as Redis.Redis,
+ {} as unknown as GlobalEventService,
+ );
+ }
+
+ @bindThis
+ public on<K extends keyof InternalEventTypes>(type: K, listener: Listener<K>, props?: ListenerProps): void {
+ if (!this._listeners.some(l => l[0] === type && l[1] === listener)) {
+ this._listeners.push([type, listener as Listener<keyof InternalEventTypes>, props ?? {}]);
+ }
+ this._calls.push(['on', [type, listener as Listener<keyof InternalEventTypes>, props]]);
+ }
+
+ @bindThis
+ public off<K extends keyof InternalEventTypes>(type: K, listener: Listener<K>): void {
+ this._listeners = this._listeners.filter(l => l[0] !== type || l[1] !== listener);
+ this._calls.push(['off', [type, listener as Listener<keyof InternalEventTypes>]]);
+ }
+
+ @bindThis
+ public async emit<K extends keyof InternalEventTypes>(type: K, value: InternalEventTypes[K], isLocal = true): Promise<void> {
+ for (const listener of this._listeners) {
+ if (listener[0] === type) {
+ if ((isLocal && !listener[2].ignoreLocal) || (!isLocal && !listener[2].ignoreRemote)) {
+ await listener[1](value, type, isLocal);
+ }
+ }
+ }
+ this._calls.push(['emit', [type, value]]);
+ }
+
+ @bindThis
+ public dispose(): void {
+ this._listeners = [];
+ this._calls.push(['dispose', []]);
+ }
+
+ @bindThis
+ public onApplicationShutdown(): void {
+ this._calls.push(['onApplicationShutdown', []]);
+ }
+}
+
diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts
index 0bf85ef8eb..34241d13cb 100644
--- a/packages/backend/test/misc/mock-resolver.ts
+++ b/packages/backend/test/misc/mock-resolver.ts
@@ -19,6 +19,7 @@ import type {
PollsRepository,
UsersRepository,
} from '@/models/_.js';
+import type { CacheService } from '@/core/CacheService.js';
import { ApLogService } from '@/core/ApLogService.js';
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
import { fromTuple } from '@/misc/from-tuple.js';
@@ -53,6 +54,7 @@ export class MockResolver extends Resolver {
loggerService,
{} as ApLogService,
{} as ApUtilityService,
+ {} as CacheService,
);
}
diff --git a/packages/backend/test/misc/noOpCaches.ts b/packages/backend/test/misc/noOpCaches.ts
new file mode 100644
index 0000000000..f3cc1e2ba2
--- /dev/null
+++ b/packages/backend/test/misc/noOpCaches.ts
@@ -0,0 +1,187 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Redis from 'ioredis';
+import { Inject } from '@nestjs/common';
+import { FakeInternalEventService } from './FakeInternalEventService.js';
+import type { BlockingsRepository, FollowingsRepository, MiUser, MutingsRepository, RenoteMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { MiLocalUser } from '@/models/User.js';
+import { MemoryKVCache, MemorySingleCache, RedisKVCache, RedisSingleCache } from '@/misc/cache.js';
+import { QuantumKVCache, QuantumKVOpts } from '@/misc/QuantumKVCache.js';
+import { CacheService, FollowStats } from '@/core/CacheService.js';
+import { DI } from '@/di-symbols.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { InternalEventService } from '@/core/InternalEventService.js';
+
+export function noOpRedis() {
+ return {
+ set: () => Promise.resolve(),
+ get: () => Promise.resolve(null),
+ del: () => Promise.resolve(),
+ on: () => {},
+ off: () => {},
+ } as unknown as Redis.Redis;
+}
+
+export class NoOpCacheService extends CacheService {
+ public readonly fakeRedis: {
+ [K in keyof Redis.Redis]: Redis.Redis[K];
+ };
+ public readonly fakeInternalEventService: FakeInternalEventService;
+
+ constructor(
+ @Inject(DI.usersRepository)
+ usersRepository: UsersRepository,
+
+ @Inject(DI.userProfilesRepository)
+ userProfilesRepository: UserProfilesRepository,
+
+ @Inject(DI.mutingsRepository)
+ mutingsRepository: MutingsRepository,
+
+ @Inject(DI.blockingsRepository)
+ blockingsRepository: BlockingsRepository,
+
+ @Inject(DI.renoteMutingsRepository)
+ renoteMutingsRepository: RenoteMutingsRepository,
+
+ @Inject(DI.followingsRepository)
+ followingsRepository: FollowingsRepository,
+
+ @Inject(UserEntityService)
+ userEntityService: UserEntityService,
+ ) {
+ const fakeRedis = noOpRedis();
+ const fakeInternalEventService = new FakeInternalEventService();
+
+ super(
+ fakeRedis,
+ fakeRedis,
+ usersRepository,
+ userProfilesRepository,
+ mutingsRepository,
+ blockingsRepository,
+ renoteMutingsRepository,
+ followingsRepository,
+ userEntityService,
+ fakeInternalEventService,
+ );
+
+ this.fakeRedis = fakeRedis;
+ this.fakeInternalEventService = fakeInternalEventService;
+
+ // Override caches
+ this.userByIdCache = new NoOpMemoryKVCache<MiUser>();
+ this.localUserByNativeTokenCache = new NoOpMemoryKVCache<MiLocalUser | null>();
+ this.localUserByIdCache = new NoOpMemoryKVCache<MiLocalUser>();
+ this.uriPersonCache = new NoOpMemoryKVCache<MiUser | null>();
+ this.userProfileCache = NoOpQuantumKVCache.copy(this.userProfileCache, fakeInternalEventService);
+ this.userMutingsCache = NoOpQuantumKVCache.copy(this.userMutingsCache, fakeInternalEventService);
+ this.userBlockingCache = NoOpQuantumKVCache.copy(this.userBlockingCache, fakeInternalEventService);
+ this.userBlockedCache = NoOpQuantumKVCache.copy(this.userBlockedCache, fakeInternalEventService);
+ this.renoteMutingsCache = NoOpQuantumKVCache.copy(this.renoteMutingsCache, fakeInternalEventService);
+ this.userFollowingsCache = NoOpQuantumKVCache.copy(this.userFollowingsCache, fakeInternalEventService);
+ this.userFollowersCache = NoOpQuantumKVCache.copy(this.userFollowersCache, fakeInternalEventService);
+ this.hibernatedUserCache = NoOpQuantumKVCache.copy(this.hibernatedUserCache, fakeInternalEventService);
+ this.userFollowStatsCache = new NoOpMemoryKVCache<FollowStats>();
+ this.translationsCache = NoOpRedisKVCache.copy(this.translationsCache, fakeRedis);
+ }
+}
+
+export class NoOpMemoryKVCache<T> extends MemoryKVCache<T> {
+ constructor() {
+ super(-1);
+ }
+}
+
+export class NoOpMemorySingleCache<T> extends MemorySingleCache<T> {
+ constructor() {
+ super(-1);
+ }
+}
+
+export class NoOpRedisKVCache<T> extends RedisKVCache<T> {
+ constructor(opts?: {
+ redis?: Redis.Redis;
+ fetcher?: RedisKVCache<T>['fetcher'];
+ toRedisConverter?: RedisKVCache<T>['toRedisConverter'];
+ fromRedisConverter?: RedisKVCache<T>['fromRedisConverter'];
+ }) {
+ super(
+ opts?.redis ?? noOpRedis(),
+ 'no-op',
+ {
+ lifetime: -1,
+ memoryCacheLifetime: -1,
+ fetcher: opts?.fetcher,
+ toRedisConverter: opts?.toRedisConverter,
+ fromRedisConverter: opts?.fromRedisConverter,
+ },
+ );
+ }
+
+ public static copy<T>(cache: RedisKVCache<T>, redis?: Redis.Redis): NoOpRedisKVCache<T> {
+ return new NoOpRedisKVCache<T>({
+ redis,
+ fetcher: cache.fetcher,
+ toRedisConverter: cache.toRedisConverter,
+ fromRedisConverter: cache.fromRedisConverter,
+ });
+ }
+}
+
+export class NoOpRedisSingleCache<T> extends RedisSingleCache<T> {
+ constructor(opts?: {
+ redis?: Redis.Redis;
+ fetcher?: RedisSingleCache<T>['fetcher'];
+ toRedisConverter?: RedisSingleCache<T>['toRedisConverter'];
+ fromRedisConverter?: RedisSingleCache<T>['fromRedisConverter'];
+ }) {
+ super(
+ opts?.redis ?? noOpRedis(),
+ 'no-op',
+ {
+ lifetime: -1,
+ memoryCacheLifetime: -1,
+ fetcher: opts?.fetcher,
+ toRedisConverter: opts?.toRedisConverter,
+ fromRedisConverter: opts?.fromRedisConverter,
+ },
+ );
+ }
+
+ public static copy<T>(cache: RedisSingleCache<T>, redis?: Redis.Redis): NoOpRedisSingleCache<T> {
+ return new NoOpRedisSingleCache<T>({
+ redis,
+ fetcher: cache.fetcher,
+ toRedisConverter: cache.toRedisConverter,
+ fromRedisConverter: cache.fromRedisConverter,
+ });
+ }
+}
+
+export class NoOpQuantumKVCache<T> extends QuantumKVCache<T> {
+ constructor(opts: Omit<QuantumKVOpts<T>, 'lifetime'> & {
+ internalEventService?: InternalEventService,
+ }) {
+ super(
+ opts.internalEventService ?? new FakeInternalEventService(),
+ 'no-op',
+ {
+ ...opts,
+ lifetime: -1,
+ },
+ );
+ }
+
+ public static copy<T>(cache: QuantumKVCache<T>, internalEventService?: InternalEventService): NoOpQuantumKVCache<T> {
+ return new NoOpQuantumKVCache<T>({
+ internalEventService,
+ fetcher: cache.fetcher,
+ bulkFetcher: cache.bulkFetcher,
+ onChanged: cache.onChanged,
+ });
+ }
+}