summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorHazelnoot <acomputerdog@gmail.com>2025-06-06 12:17:04 -0400
committerHazelnoot <acomputerdog@gmail.com>2025-06-09 11:02:36 -0400
commit2e486f02ffb83192fd46fd231fb3604e1b982416 (patch)
treefe351c8793f32e06c55e25c6f957e5decd83a9c8 /packages
parentfix reversed isBlocking / isBlocked in UserEntityService.getRelation (diff)
downloadsharkey-2e486f02ffb83192fd46fd231fb3604e1b982416.tar.gz
sharkey-2e486f02ffb83192fd46fd231fb3604e1b982416.tar.bz2
sharkey-2e486f02ffb83192fd46fd231fb3604e1b982416.zip
implement no-op caches for testing
Diffstat (limited to 'packages')
-rw-r--r--packages/backend/src/core/CacheService.ts22
-rw-r--r--packages/backend/src/misc/cache.ts41
-rw-r--r--packages/backend/test/misc/noOpCaches.ts196
3 files changed, 244 insertions, 15 deletions
diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts
index ae24a9721f..2c136eac2b 100644
--- a/packages/backend/src/core/CacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -28,7 +28,7 @@ export interface CachedTranslation {
text: string | undefined;
}
-interface CachedTranslationEntity {
+export interface CachedTranslationEntity {
l?: string;
t?: string;
u?: number;
@@ -46,8 +46,8 @@ export class CacheService implements OnApplicationShutdown {
public userBlockedCache: QuantumKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
public renoteMutingsCache: QuantumKVCache<Set<string>>;
public userFollowingsCache: QuantumKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
- private readonly userFollowStatsCache = new MemoryKVCache<FollowStats>(1000 * 60 * 10); // 10 minutes
- private readonly translationsCache: RedisKVCache<CachedTranslationEntity>;
+ protected userFollowStatsCache = new MemoryKVCache<FollowStats>(1000 * 60 * 10); // 10 minutes
+ protected translationsCache: RedisKVCache<CachedTranslationEntity>;
constructor(
@Inject(DI.redis)
@@ -468,6 +468,22 @@ export class CacheService implements OnApplicationShutdown {
}
@bindThis
+ public clear(): void {
+ this.userByIdCache.clear();
+ this.localUserByNativeTokenCache.clear();
+ this.localUserByIdCache.clear();
+ this.uriPersonCache.clear();
+ this.userProfileCache.clear();
+ this.userMutingsCache.clear();
+ this.userBlockingCache.clear();
+ this.userBlockedCache.clear();
+ this.renoteMutingsCache.clear();
+ this.userFollowingsCache.clear();
+ this.userFollowStatsCache.clear();
+ this.translationsCache.clear();
+ }
+
+ @bindThis
public dispose(): void {
this.internalEventService.off('userChangeSuspendedState', this.onUserEvent);
this.internalEventService.off('userChangeDeletedState', this.onUserEvent);
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index 3145550a44..0a1cf6adb4 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -11,9 +11,9 @@ import { InternalEventTypes } from '@/core/GlobalEventService.js';
export class RedisKVCache<T> {
private readonly lifetime: number;
private readonly memoryCache: MemoryKVCache<T>;
- private readonly fetcher: (key: string) => Promise<T>;
- private readonly toRedisConverter: (value: T) => string;
- private readonly fromRedisConverter: (value: string) => T | undefined;
+ public readonly fetcher: (key: string) => Promise<T>;
+ public readonly toRedisConverter: (value: T) => string;
+ public readonly fromRedisConverter: (value: string) => T | undefined;
constructor(
private redisClient: Redis.Redis,
@@ -102,6 +102,11 @@ export class RedisKVCache<T> {
}
@bindThis
+ public clear() {
+ this.memoryCache.clear();
+ }
+
+ @bindThis
public gc() {
this.memoryCache.gc();
}
@@ -125,16 +130,17 @@ export class RedisSingleCache<T> {
opts: {
lifetime: number;
memoryCacheLifetime: number;
- fetcher: RedisSingleCache<T>['fetcher'];
- toRedisConverter: RedisSingleCache<T>['toRedisConverter'];
- fromRedisConverter: RedisSingleCache<T>['fromRedisConverter'];
+ fetcher?: RedisSingleCache<T>['fetcher'];
+ toRedisConverter?: RedisSingleCache<T>['toRedisConverter'];
+ fromRedisConverter?: RedisSingleCache<T>['fromRedisConverter'];
},
) {
this.lifetime = opts.lifetime;
this.memoryCache = new MemorySingleCache(opts.memoryCacheLifetime);
- this.fetcher = opts.fetcher;
- this.toRedisConverter = opts.toRedisConverter;
- this.fromRedisConverter = opts.fromRedisConverter;
+
+ this.fetcher = opts.fetcher ?? (() => { throw new Error('fetch not supported - use get/set directly'); });
+ this.toRedisConverter = opts.toRedisConverter ?? ((value) => JSON.stringify(value));
+ this.fromRedisConverter = opts.fromRedisConverter ?? ((value) => JSON.parse(value));
}
@bindThis
@@ -417,6 +423,8 @@ export class MemorySingleCache<T> {
}
}
+// TODO move to separate file
+
export interface QuantumKVOpts<T> {
/**
* Memory cache lifetime in milliseconds.
@@ -452,9 +460,9 @@ export interface QuantumKVOpts<T> {
export class QuantumKVCache<T> implements Iterable<[key: string, value: T]> {
private readonly memoryCache: MemoryKVCache<T>;
- private readonly fetcher: QuantumKVOpts<T>['fetcher'];
- private readonly onSet: QuantumKVOpts<T>['onSet'];
- private readonly onDelete: QuantumKVOpts<T>['onDelete'];
+ public readonly fetcher: QuantumKVOpts<T>['fetcher'];
+ public readonly onSet: QuantumKVOpts<T>['onSet'];
+ public readonly onDelete: QuantumKVOpts<T>['onDelete'];
/**
* @param internalEventService Service bus to synchronize events.
@@ -676,6 +684,15 @@ export class QuantumKVCache<T> implements Iterable<[key: string, value: T]> {
* Does not send any events or update other processes.
*/
@bindThis
+ public clear() {
+ this.memoryCache.clear();
+ }
+
+ /**
+ * Removes expired cache entries from the local view.
+ * Does not send any events or update other processes.
+ */
+ @bindThis
public gc() {
this.memoryCache.gc();
}
diff --git a/packages/backend/test/misc/noOpCaches.ts b/packages/backend/test/misc/noOpCaches.ts
new file mode 100644
index 0000000000..373c7bddcc
--- /dev/null
+++ b/packages/backend/test/misc/noOpCaches.ts
@@ -0,0 +1,196 @@
+/*
+ * 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, MiFollowing, MiUser, MiUserProfile, MutingsRepository, RenoteMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { MiLocalUser } from '@/models/User.js';
+import { MemoryKVCache, MemorySingleCache, QuantumKVCache, QuantumKVOpts, RedisKVCache, RedisSingleCache } from '@/misc/cache.js';
+import { CacheService, CachedTranslationEntity, FollowStats } from '@/core/CacheService.js';
+import { DI } from '@/di-symbols.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.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 = new NoOpQuantumKVCache<MiUserProfile>({
+ internalEventService: fakeInternalEventService,
+ fetcher: this.userProfileCache.fetcher,
+ onSet: this.userProfileCache.onSet,
+ onDelete: this.userProfileCache.onDelete,
+ });
+ this.userMutingsCache = new NoOpQuantumKVCache<Set<string>>({
+ internalEventService: fakeInternalEventService,
+ fetcher: this.userMutingsCache.fetcher,
+ onSet: this.userMutingsCache.onSet,
+ onDelete: this.userMutingsCache.onDelete,
+ });
+ this.userBlockingCache = new NoOpQuantumKVCache<Set<string>>({
+ internalEventService: fakeInternalEventService,
+ fetcher: this.userBlockingCache.fetcher,
+ onSet: this.userBlockingCache.onSet,
+ onDelete: this.userBlockingCache.onDelete,
+ });
+ this.userBlockedCache = new NoOpQuantumKVCache<Set<string>>({
+ internalEventService: fakeInternalEventService,
+ fetcher: this.userBlockedCache.fetcher,
+ onSet: this.userBlockedCache.onSet,
+ onDelete: this.userBlockedCache.onDelete,
+ });
+ this.renoteMutingsCache = new NoOpQuantumKVCache<Set<string>>({
+ internalEventService: fakeInternalEventService,
+ fetcher: this.renoteMutingsCache.fetcher,
+ onSet: this.renoteMutingsCache.onSet,
+ onDelete: this.renoteMutingsCache.onDelete,
+ });
+ this.userFollowingsCache = new NoOpQuantumKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>({
+ internalEventService: fakeInternalEventService,
+ fetcher: this.userFollowingsCache.fetcher,
+ onSet: this.userFollowingsCache.onSet,
+ onDelete: this.userFollowingsCache.onDelete,
+ });
+ this.userFollowStatsCache = new NoOpMemoryKVCache<FollowStats>();
+ this.translationsCache = new NoOpRedisKVCache<CachedTranslationEntity>({
+ redis: fakeRedis,
+ fetcher: this.translationsCache.fetcher,
+ toRedisConverter: this.translationsCache.toRedisConverter,
+ fromRedisConverter: this.translationsCache.fromRedisConverter,
+ });
+ }
+}
+
+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,
+ },
+ );
+ }
+}
+
+export class NoOpRedisSingleCache<T> extends RedisSingleCache<T> {
+ constructor(opts?: {
+ fakeRedis?: Redis.Redis;
+ fetcher?: RedisSingleCache<T>['fetcher'];
+ toRedisConverter?: RedisSingleCache<T>['toRedisConverter'];
+ fromRedisConverter?: RedisSingleCache<T>['fromRedisConverter'];
+ }) {
+ super(
+ opts?.fakeRedis ?? noOpRedis(),
+ 'no-op',
+ {
+ lifetime: -1,
+ memoryCacheLifetime: -1,
+ fetcher: opts?.fetcher,
+ toRedisConverter: opts?.toRedisConverter,
+ fromRedisConverter: opts?.fromRedisConverter,
+ },
+ );
+ }
+}
+
+export class NoOpQuantumKVCache<T> extends QuantumKVCache<T> {
+ constructor(opts: {
+ internalEventService?: FakeInternalEventService,
+ fetcher: QuantumKVOpts<T>['fetcher'],
+ onSet?: QuantumKVOpts<T>['onSet'],
+ onDelete?: QuantumKVOpts<T>['onDelete'],
+ }) {
+ super(
+ opts.internalEventService ?? new FakeInternalEventService(),
+ 'no-op',
+ {
+ lifetime: -1,
+ fetcher: opts.fetcher,
+ onSet: opts.onSet,
+ onDelete: opts.onDelete,
+ },
+ );
+ }
+}