feat: 增加BangDream主数据同步任务
This commit is contained in:
parent
62161bac8f
commit
0263b423c2
@ -1,5 +1,11 @@
|
|||||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
import {
|
||||||
import { join } from 'node:path';
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
readFileSync,
|
||||||
|
renameSync,
|
||||||
|
writeFileSync,
|
||||||
|
} from 'node:fs';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
import { parentPort, workerData } from 'node:worker_threads';
|
import { parentPort, workerData } from 'node:worker_threads';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import type {
|
import type {
|
||||||
@ -330,6 +336,10 @@ function createBangDreamRuntimeIo(): BangDreamRuntimeIo {
|
|||||||
readExcelRows: async (filePath) => readExcelRows(filePath),
|
readExcelRows: async (filePath) => readExcelRows(filePath),
|
||||||
readJsonFile: async (filePath) => readJsonFile(filePath),
|
readJsonFile: async (filePath) => readJsonFile(filePath),
|
||||||
readJsonFileSync: (filePath) => readJsonFile(filePath),
|
readJsonFileSync: (filePath) => readJsonFile(filePath),
|
||||||
|
renameFile: async (from, to) => {
|
||||||
|
mkdirSync(dirname(to), { recursive: true });
|
||||||
|
renameSync(from, to);
|
||||||
|
},
|
||||||
requestArrayBuffer: async (url, options) => ({
|
requestArrayBuffer: async (url, options) => ({
|
||||||
body: Buffer.from(
|
body: Buffer.from(
|
||||||
await callHost<Uint8Array>('requestBuffer', {
|
await callHost<Uint8Array>('requestBuffer', {
|
||||||
@ -361,8 +371,10 @@ function createBangDreamRuntimeIo(): BangDreamRuntimeIo {
|
|||||||
}),
|
}),
|
||||||
sleep: async (ms) =>
|
sleep: async (ms) =>
|
||||||
await new Promise((resolve) => setTimeout(resolve, ms)),
|
await new Promise((resolve) => setTimeout(resolve, ms)),
|
||||||
writeJsonFile: async (filePath, data) =>
|
writeJsonFile: async (filePath, data) => {
|
||||||
writeFileSync(filePath, JSON.stringify(data)),
|
mkdirSync(dirname(filePath), { recursive: true });
|
||||||
|
writeFileSync(filePath, JSON.stringify(data));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,8 @@
|
|||||||
"runtime.http",
|
"runtime.http",
|
||||||
"asset.read",
|
"asset.read",
|
||||||
"plugin.config.read",
|
"plugin.config.read",
|
||||||
"plugin.storage.read"
|
"plugin.storage.read",
|
||||||
|
"plugin.storage.write"
|
||||||
],
|
],
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"workerType": "node-worker",
|
"workerType": "node-worker",
|
||||||
@ -257,6 +258,22 @@
|
|||||||
"timeoutMs": 30000
|
"timeoutMs": 30000
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"key": "bangdream.bestdori.sync-main-data",
|
||||||
|
"name": "同步 Bestdori 主数据",
|
||||||
|
"handlerName": "syncBestdoriMainData",
|
||||||
|
"description": "同步 BangDream 重命令依赖的 Bestdori JSON 主数据。",
|
||||||
|
"defaultCron": "0 */6 * * *",
|
||||||
|
"enabled": true,
|
||||||
|
"timeoutMs": 120000,
|
||||||
|
"permissions": [
|
||||||
|
"runtime.http",
|
||||||
|
"plugin.storage.read",
|
||||||
|
"plugin.storage.write"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"events": [],
|
"events": [],
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { join } from 'node:path';
|
||||||
import { bestdoriApiPath } from '@/modules/qqbot/plugins/bangdream/src/config/runtime-config';
|
import { bestdoriApiPath } from '@/modules/qqbot/plugins/bangdream/src/config/runtime-config';
|
||||||
import { bangdreamBestdoriProvider } from '@/modules/qqbot/plugins/bangdream/src/infrastructure/integration/bestdori.provider';
|
import { bangdreamBestdoriProvider } from '@/modules/qqbot/plugins/bangdream/src/infrastructure/integration/bestdori.provider';
|
||||||
import { bangdreamStaticPatchProvider } from '@/modules/qqbot/plugins/bangdream/src/infrastructure/storage/static-patch.provider';
|
import { bangdreamStaticPatchProvider } from '@/modules/qqbot/plugins/bangdream/src/infrastructure/storage/static-patch.provider';
|
||||||
@ -7,6 +8,7 @@ import {
|
|||||||
normalizeBangDreamPositiveInteger,
|
normalizeBangDreamPositiveInteger,
|
||||||
} from '@/modules/qqbot/plugins/bangdream/src/config/runtime-options';
|
} from '@/modules/qqbot/plugins/bangdream/src/config/runtime-options';
|
||||||
import {
|
import {
|
||||||
|
readBangDreamJsonFile,
|
||||||
readBangDreamRuntimeConfig,
|
readBangDreamRuntimeConfig,
|
||||||
sleepBangDreamRuntime,
|
sleepBangDreamRuntime,
|
||||||
} from '@/modules/qqbot/plugins/bangdream/src/infrastructure/integration/runtime-io';
|
} from '@/modules/qqbot/plugins/bangdream/src/infrastructure/integration/runtime-io';
|
||||||
@ -82,12 +84,18 @@ async function loadCatalogKey(key: BangDreamCatalogKey, useCache: boolean) {
|
|||||||
|
|
||||||
const promise = (async () => {
|
const promise = (async () => {
|
||||||
if (useCache) {
|
if (useCache) {
|
||||||
bangdreamCatalogCache[key] = await bangdreamBestdoriProvider.getJson(
|
try {
|
||||||
bestdoriApiPath[key],
|
bangdreamCatalogCache[key] = await readBangDreamCatalogDataFromCache(
|
||||||
{
|
key,
|
||||||
cacheTime: 1 / 0,
|
);
|
||||||
},
|
} catch {
|
||||||
);
|
bangdreamCatalogCache[key] = await bangdreamBestdoriProvider.getJson(
|
||||||
|
bestdoriApiPath[key],
|
||||||
|
{
|
||||||
|
cacheTime: 1 / 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,4 +192,34 @@ export async function waitForBangDreamCatalogReady(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function refreshBangDreamCatalogFromCache(
|
||||||
|
keys?: readonly BangDreamCatalogKey[],
|
||||||
|
) {
|
||||||
|
const catalogKeys = normalizeCatalogKeys(keys);
|
||||||
|
for (const key of catalogKeys) {
|
||||||
|
bangdreamCatalogCache[key] = {};
|
||||||
|
}
|
||||||
|
await loadCatalogData(catalogKeys, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveBangDreamMainDataCachePath(
|
||||||
|
cacheRoot: string,
|
||||||
|
key: BangDreamCatalogKey,
|
||||||
|
) {
|
||||||
|
return join(cacheRoot, 'bestdori', `${key}.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveBangDreamCatalogCacheRoot() {
|
||||||
|
return (
|
||||||
|
readBangDreamRuntimeConfig(BANGDREAM_TSUGU_ENV_KEYS.cacheRoot) ||
|
||||||
|
join(process.cwd(), '.kt-workspace', 'cache', 'bangdream')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readBangDreamCatalogDataFromCache(key: BangDreamCatalogKey) {
|
||||||
|
return readBangDreamJsonFile(
|
||||||
|
resolveBangDreamMainDataCachePath(resolveBangDreamCatalogCacheRoot(), key),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default bangdreamCatalogCache;
|
export default bangdreamCatalogCache;
|
||||||
|
|||||||
@ -0,0 +1,119 @@
|
|||||||
|
import { join } from 'node:path';
|
||||||
|
import { bestdoriApiPath, bestdoriUrl } from '../../config/runtime-config';
|
||||||
|
import { BANGDREAM_TSUGU_ENV_KEYS } from '../../config/runtime-options';
|
||||||
|
import {
|
||||||
|
readBangDreamRuntimeConfig,
|
||||||
|
requestBangDreamJson,
|
||||||
|
writeBangDreamJsonFileAtomic,
|
||||||
|
} from '../../infrastructure/integration/runtime-io';
|
||||||
|
import {
|
||||||
|
refreshBangDreamCatalogFromCache,
|
||||||
|
resolveBangDreamMainDataCachePath,
|
||||||
|
type BangDreamCatalogKey,
|
||||||
|
} from '../catalog/bangdream-catalog-cache';
|
||||||
|
|
||||||
|
export const BANGDREAM_BESTDORI_MAIN_DATA_KEYS = [
|
||||||
|
'songs',
|
||||||
|
'meta',
|
||||||
|
'cards',
|
||||||
|
'skills',
|
||||||
|
'events',
|
||||||
|
'gacha',
|
||||||
|
'costumes',
|
||||||
|
'bands',
|
||||||
|
'characters',
|
||||||
|
'areaItems',
|
||||||
|
] as const satisfies readonly BangDreamCatalogKey[];
|
||||||
|
|
||||||
|
type BangDreamBestdoriMainDataKey =
|
||||||
|
(typeof BANGDREAM_BESTDORI_MAIN_DATA_KEYS)[number];
|
||||||
|
|
||||||
|
type BangDreamBestdoriMainDataSyncOutput = {
|
||||||
|
cacheRootConfigured: boolean;
|
||||||
|
durationMs: number;
|
||||||
|
failedCount: number;
|
||||||
|
successCount: number;
|
||||||
|
syncedKeys: BangDreamBestdoriMainDataKey[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createBestdoriMainDataSyncTask() {
|
||||||
|
return {
|
||||||
|
execute: syncBestdoriMainData,
|
||||||
|
handlerName: 'syncBestdoriMainData',
|
||||||
|
key: 'bangdream.bestdori.sync-main-data',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncBestdoriMainData(
|
||||||
|
input: Record<string, unknown>,
|
||||||
|
): Promise<BangDreamBestdoriMainDataSyncOutput> {
|
||||||
|
const startedAt = Date.now();
|
||||||
|
const keys = normalizeKeys(input.keys);
|
||||||
|
const cacheRoot = resolveCacheRoot();
|
||||||
|
const failures: Array<{ key: string; message: string }> = [];
|
||||||
|
const syncedKeys: BangDreamBestdoriMainDataKey[] = [];
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
try {
|
||||||
|
const response = await requestBangDreamJson(
|
||||||
|
new URL(bestdoriApiPath[key], bestdoriUrl).toString(),
|
||||||
|
{ timeoutMs: 30000 },
|
||||||
|
);
|
||||||
|
await writeBangDreamJsonFileAtomic(
|
||||||
|
resolveBangDreamMainDataCachePath(cacheRoot, key),
|
||||||
|
response.body,
|
||||||
|
);
|
||||||
|
syncedKeys.push(key);
|
||||||
|
} catch (error) {
|
||||||
|
failures.push({
|
||||||
|
key,
|
||||||
|
message: error instanceof Error ? error.message : `${error}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (syncedKeys.length > 0) {
|
||||||
|
await refreshBangDreamCatalogFromCache(syncedKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`BangDream Bestdori 主数据同步失败:${failures
|
||||||
|
.map((failure) => `${failure.key}:${failure.message}`)
|
||||||
|
.join('; ')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cacheRootConfigured: Boolean(
|
||||||
|
readBangDreamRuntimeConfig(BANGDREAM_TSUGU_ENV_KEYS.cacheRoot),
|
||||||
|
),
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
failedCount: failures.length,
|
||||||
|
successCount: syncedKeys.length,
|
||||||
|
syncedKeys,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeKeys(input: unknown): BangDreamBestdoriMainDataKey[] {
|
||||||
|
const requested = Array.isArray(input) ? input : [];
|
||||||
|
const allowed = new Set<string>(BANGDREAM_BESTDORI_MAIN_DATA_KEYS);
|
||||||
|
const keys =
|
||||||
|
requested.length > 0
|
||||||
|
? requested.filter(
|
||||||
|
(key): key is BangDreamBestdoriMainDataKey =>
|
||||||
|
typeof key === 'string' && allowed.has(key),
|
||||||
|
)
|
||||||
|
: BANGDREAM_BESTDORI_MAIN_DATA_KEYS;
|
||||||
|
const uniqueKeys = [...new Set(keys)];
|
||||||
|
return uniqueKeys.length > 0
|
||||||
|
? uniqueKeys
|
||||||
|
: [...BANGDREAM_BESTDORI_MAIN_DATA_KEYS];
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCacheRoot() {
|
||||||
|
return (
|
||||||
|
readBangDreamRuntimeConfig(BANGDREAM_TSUGU_ENV_KEYS.cacheRoot) ||
|
||||||
|
join(process.cwd(), '.kt-workspace', 'cache', 'bangdream')
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './bestdori-main-data-sync.task';
|
||||||
@ -11,6 +11,7 @@ import {
|
|||||||
configureBangDreamRuntimeIo,
|
configureBangDreamRuntimeIo,
|
||||||
type BangDreamRuntimeIo,
|
type BangDreamRuntimeIo,
|
||||||
} from './infrastructure/integration/runtime-io';
|
} from './infrastructure/integration/runtime-io';
|
||||||
|
import { createBestdoriMainDataSyncTask } from './application/tasks';
|
||||||
import {
|
import {
|
||||||
getBangDreamOperationsByHandlerName,
|
getBangDreamOperationsByHandlerName,
|
||||||
type BangDreamOperationModule,
|
type BangDreamOperationModule,
|
||||||
@ -58,6 +59,7 @@ export function createPlugin(options: BangDreamPluginRuntimeOptions) {
|
|||||||
createBangDreamOperationLogObserver(),
|
createBangDreamOperationLogObserver(),
|
||||||
]);
|
]);
|
||||||
const operationsByKey = resolveBangDreamOperations(options.operations);
|
const operationsByKey = resolveBangDreamOperations(options.operations);
|
||||||
|
const tasks = [createBestdoriMainDataSyncTask()];
|
||||||
const normalizeError =
|
const normalizeError =
|
||||||
options.normalizeError ||
|
options.normalizeError ||
|
||||||
((error: unknown) =>
|
((error: unknown) =>
|
||||||
@ -120,6 +122,7 @@ export function createPlugin(options: BangDreamPluginRuntimeOptions) {
|
|||||||
execute: async (input: BangDreamCommandInput) =>
|
execute: async (input: BangDreamCommandInput) =>
|
||||||
await executeOperation(operation.key, input),
|
await executeOperation(operation.key, input),
|
||||||
})),
|
})),
|
||||||
|
tasks,
|
||||||
version: options.version || '2.0.0',
|
version: options.version || '2.0.0',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ export type BangDreamRuntimeIo = {
|
|||||||
) => Promise<T[]>;
|
) => Promise<T[]>;
|
||||||
readJsonFile?: <T = unknown>(filePath: string) => Promise<T>;
|
readJsonFile?: <T = unknown>(filePath: string) => Promise<T>;
|
||||||
readJsonFileSync?: <T = unknown>(filePath: string) => T;
|
readJsonFileSync?: <T = unknown>(filePath: string) => T;
|
||||||
|
renameFile?: (from: string, to: string) => Promise<void>;
|
||||||
requestArrayBuffer?: (
|
requestArrayBuffer?: (
|
||||||
url: string,
|
url: string,
|
||||||
options?: {
|
options?: {
|
||||||
@ -111,6 +112,18 @@ export async function writeBangDreamJsonFile(filePath: string, data: unknown) {
|
|||||||
await runtimeIo.writeJsonFile(filePath, data);
|
await runtimeIo.writeJsonFile(filePath, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function writeBangDreamJsonFileAtomic(
|
||||||
|
filePath: string,
|
||||||
|
data: unknown,
|
||||||
|
) {
|
||||||
|
if (!runtimeIo.writeJsonFile || !runtimeIo.renameFile) {
|
||||||
|
throw new Error('BangDream JSON 原子写入器未初始化');
|
||||||
|
}
|
||||||
|
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
||||||
|
await runtimeIo.writeJsonFile(tempPath, data);
|
||||||
|
await runtimeIo.renameFile(tempPath, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
export async function sleepBangDreamRuntime(ms: number) {
|
export async function sleepBangDreamRuntime(ms: number) {
|
||||||
if (ms <= 0) return;
|
if (ms <= 0) return;
|
||||||
if (runtimeIo.sleep) {
|
if (runtimeIo.sleep) {
|
||||||
|
|||||||
@ -0,0 +1,110 @@
|
|||||||
|
import {
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
mkdtempSync,
|
||||||
|
readFileSync,
|
||||||
|
renameSync,
|
||||||
|
rmSync,
|
||||||
|
writeFileSync,
|
||||||
|
} from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import {
|
||||||
|
BANGDREAM_BESTDORI_MAIN_DATA_KEYS,
|
||||||
|
createBestdoriMainDataSyncTask,
|
||||||
|
} from '../../../../../src/modules/qqbot/plugins/bangdream/src/application/tasks';
|
||||||
|
import { BANGDREAM_TSUGU_ENV_KEYS } from '../../../../../src/modules/qqbot/plugins/bangdream/src/config/runtime-options';
|
||||||
|
import { configureBangDreamRuntimeIo } from '../../../../../src/modules/qqbot/plugins/bangdream/src/infrastructure/integration/runtime-io';
|
||||||
|
|
||||||
|
describe('BangDream Bestdori main-data sync task', () => {
|
||||||
|
let cacheRoot: string;
|
||||||
|
let consoleInfoSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cacheRoot = mkdtempSync(join(tmpdir(), 'bangdream-sync-'));
|
||||||
|
consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
consoleInfoSpy.mockRestore();
|
||||||
|
rmSync(cacheRoot, { force: true, recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('downloads main JSON data, writes cache atomically, and returns safe summary', async () => {
|
||||||
|
const requestedUrls: string[] = [];
|
||||||
|
configureBangDreamRuntimeIo({
|
||||||
|
getConfig: (key) =>
|
||||||
|
key === BANGDREAM_TSUGU_ENV_KEYS.cacheRoot ? cacheRoot : undefined,
|
||||||
|
readJsonFile: async (filePath) =>
|
||||||
|
JSON.parse(readFileSync(filePath, 'utf8')),
|
||||||
|
readExcelRows: async () => [],
|
||||||
|
renameFile: async (from, to) => {
|
||||||
|
mkdirSync(dirname(to), { recursive: true });
|
||||||
|
renameSync(from, to);
|
||||||
|
},
|
||||||
|
requestJson: async <T = unknown>(url: string) => {
|
||||||
|
requestedUrls.push(`${url}`);
|
||||||
|
return { body: { ok: true, url } as T };
|
||||||
|
},
|
||||||
|
writeJsonFile: async (filePath, data) => {
|
||||||
|
mkdirSync(dirname(filePath), { recursive: true });
|
||||||
|
writeFileSync(filePath, `${JSON.stringify(data)}\n`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const task = createBestdoriMainDataSyncTask();
|
||||||
|
const output = await task.execute({ keys: ['songs', 'meta'] });
|
||||||
|
|
||||||
|
expect(output).toMatchObject({
|
||||||
|
failedCount: 0,
|
||||||
|
successCount: 2,
|
||||||
|
syncedKeys: ['songs', 'meta'],
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
readFileSync(join(cacheRoot, 'bestdori', 'songs.json'), 'utf8'),
|
||||||
|
).toContain('"ok":true');
|
||||||
|
expect(
|
||||||
|
readFileSync(join(cacheRoot, 'bestdori', 'meta.json'), 'utf8'),
|
||||||
|
).toContain('"ok":true');
|
||||||
|
expect(requestedUrls).toHaveLength(2);
|
||||||
|
expect(JSON.stringify(output)).not.toContain('/api/songs/all');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps existing cache file when one key fails', async () => {
|
||||||
|
const metaCachePath = join(cacheRoot, 'bestdori', 'meta.json');
|
||||||
|
mkdirSync(dirname(metaCachePath), { recursive: true });
|
||||||
|
writeFileSync(metaCachePath, '{"previous":true}\n');
|
||||||
|
configureBangDreamRuntimeIo({
|
||||||
|
getConfig: (key) =>
|
||||||
|
key === BANGDREAM_TSUGU_ENV_KEYS.cacheRoot ? cacheRoot : undefined,
|
||||||
|
readJsonFile: async (filePath) =>
|
||||||
|
JSON.parse(readFileSync(filePath, 'utf8')),
|
||||||
|
readExcelRows: async () => [],
|
||||||
|
renameFile: async (from, to) => {
|
||||||
|
mkdirSync(dirname(to), { recursive: true });
|
||||||
|
renameSync(from, to);
|
||||||
|
},
|
||||||
|
requestJson: async <T = unknown>(url: string) => {
|
||||||
|
if (`${url}`.includes('/api/songs/meta/')) {
|
||||||
|
throw new Error('network failed');
|
||||||
|
}
|
||||||
|
return { body: { ok: true } as T };
|
||||||
|
},
|
||||||
|
writeJsonFile: async (filePath, data) => {
|
||||||
|
mkdirSync(dirname(filePath), { recursive: true });
|
||||||
|
writeFileSync(filePath, `${JSON.stringify(data)}\n`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const task = createBestdoriMainDataSyncTask();
|
||||||
|
await expect(task.execute({ keys: ['songs', 'meta'] })).rejects.toThrow(
|
||||||
|
'BangDream Bestdori 主数据同步失败',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readFileSync(metaCachePath, 'utf8')).toContain('"previous":true');
|
||||||
|
expect(existsSync(join(cacheRoot, 'bestdori', 'songs.json'))).toBe(true);
|
||||||
|
expect(BANGDREAM_BESTDORI_MAIN_DATA_KEYS).toEqual(
|
||||||
|
expect.arrayContaining(['songs', 'meta', 'cards', 'skills', 'events']),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user