test: 固化Bilibili卡片插件架构门禁

This commit is contained in:
sunlei 2026-06-19 19:28:38 +08:00
parent 98e26616bb
commit 414136a7bd
5 changed files with 163 additions and 31 deletions

View File

@ -249,12 +249,81 @@ INSERT INTO qqbot_plugin (
'Repeater',
'Built-in repeater event plugin metadata.',
'installed'
),
(
1000000000000000105,
'bilibili-card',
'Bilibili Card',
'Built-in Bilibili card event plugin metadata.',
'installed'
)
ON DUPLICATE KEY UPDATE
plugin_name = VALUES(plugin_name),
description = VALUES(description),
status = VALUES(status);
INSERT INTO qqbot_plugin_version (
id,
plugin_id,
version,
package_hash,
manifest_json
) VALUES (
1000000000000001105,
1000000000000000105,
'1.0.0',
'builtin-bilibili-card-1.0.0',
JSON_OBJECT(
'key', 'bilibili-card',
'name', 'Bilibili Card',
'version', '1.0.0',
'entry', 'src/index.ts',
'events', JSON_ARRAY(JSON_OBJECT(
'key', 'bilibili-card.message',
'eventName', 'message',
'handlerName', 'handleMessage',
'name', 'Bilibili 卡片解析'
))
)
) ON DUPLICATE KEY UPDATE
package_hash = VALUES(package_hash),
manifest_json = VALUES(manifest_json);
INSERT INTO qqbot_plugin_installation (
id,
plugin_id,
version_id,
status,
runtime_status,
installed_path
) VALUES (
1000000000000001205,
1000000000000000105,
1000000000000001105,
'installed',
'idle',
'src/modules/qqbot/plugins/bilibili-card'
) ON DUPLICATE KEY UPDATE
status = VALUES(status),
runtime_status = VALUES(runtime_status),
installed_path = VALUES(installed_path);
INSERT INTO qqbot_plugin_event_handler (
id,
plugin_id,
event_key,
handler_name,
enabled
) VALUES (
1000000000000001305,
1000000000000000105,
'bilibili-card.message',
'handleMessage',
1
) ON DUPLICATE KEY UPDATE
handler_name = VALUES(handler_name),
enabled = VALUES(enabled);
INSERT INTO qqbot_command (
id,
operation_key,

View File

@ -36,6 +36,17 @@ FROM qqbot_plugin
WHERE plugin_key = 'bangdream'
AND status = 'installed';
SELECT 'seed_qqbot_plugin_bilibili_card' AS check_name, COUNT(*) AS matched_rows
FROM qqbot_plugin
WHERE plugin_key = 'bilibili-card'
AND status = 'installed';
SELECT 'seed_qqbot_plugin_event_bilibili_card' AS check_name, COUNT(*) AS matched_rows
FROM qqbot_plugin_event_handler
WHERE event_key = 'bilibili-card.message'
AND handler_name = 'handleMessage'
AND enabled = 1;
SELECT 'seed_qqbot_command_bangdream_song' AS check_name, COUNT(*) AS matched_rows
FROM qqbot_command
WHERE command_key = 'bangdream_song'

View File

@ -142,6 +142,7 @@ describe('QQBot current operation matrix', () => {
it('freezes built-in plugin manifests and exposed capabilities', () => {
const manifests = {
bangdream: readManifest('bangdream'),
bilibiliCard: readManifest('bilibili-card'),
ff14: readManifest('ff14-market'),
fflogs: readManifest('fflogs'),
repeater: readManifest('repeater'),
@ -157,6 +158,23 @@ describe('QQBot current operation matrix', () => {
})),
).toEqual(bangdreamOperations);
expect(
manifests.bilibiliCard.events.map((event) => ({
eventName: event.eventName,
handlerName: event.handlerName,
key: event.key,
name: event.name,
})),
).toEqual([
{
eventName: 'message',
handlerName: 'handleMessage',
key: 'bilibili-card.message',
name: 'Bilibili 卡片解析',
},
]);
expect(manifests.bilibiliCard.operations).toEqual([]);
expect(
manifests.ff14.operations.map((operation) => ({
aliases: operation.aliases,
@ -215,6 +233,10 @@ describe('QQBot current operation matrix', () => {
it('freezes current online command seed linkage for command plugins', () => {
const seedSql = readFileSync(join(repoRoot, 'sql/qqbot-init.sql'), 'utf8');
const refactorSeedSql = readFileSync(
join(repoRoot, 'sql/refactor-v3/01-seed-core.sql'),
'utf8',
);
for (const operation of bangdreamOperations) {
expect(seedSql).toContain(`'${operation.key}'`);
@ -224,5 +246,7 @@ describe('QQBot current operation matrix', () => {
expect(seedSql).toContain(`'ff14-market', 'ff14.market.price'`);
expect(seedSql).toContain(`'fflogs', 'fflogs.character.summary'`);
expect(seedSql).toContain(`'bangdream', 'bangdream.event.stage', 'plain'`);
expect(refactorSeedSql).toContain(`'bilibili-card'`);
expect(refactorSeedSql).toContain(`'bilibili-card.message'`);
});
});

View File

@ -97,6 +97,7 @@ describe('QQBot plugin package boundary', () => {
expect(pluginDirs).toEqual([
'bangdream',
'bilibili-card',
'ff14-market',
'fflogs',
'repeater',
@ -104,23 +105,27 @@ describe('QQBot plugin package boundary', () => {
});
it('uses the same package shape for every built-in plugin', () => {
const missing = ['bangdream', 'ff14-market', 'fflogs', 'repeater'].flatMap(
(pluginKey) => {
const manifest = JSON.parse(
readFileSync(join(pluginRoot, pluginKey, 'plugin.json'), 'utf8'),
) as { events?: unknown[]; operations?: unknown[] };
const requiredPaths = [
...requiredPluginPaths,
...((manifest.operations || []).length
? requiredCommandPluginPaths
: []),
...((manifest.events || []).length ? requiredEventPluginPaths : []),
];
return requiredPaths
.map((pathName) => `${pluginKey}/${pathName}`)
.filter((pathName) => !existsSync(join(pluginRoot, pathName)));
},
);
const missing = [
'bangdream',
'bilibili-card',
'ff14-market',
'fflogs',
'repeater',
].flatMap((pluginKey) => {
const manifest = JSON.parse(
readFileSync(join(pluginRoot, pluginKey, 'plugin.json'), 'utf8'),
) as { events?: unknown[]; operations?: unknown[] };
const requiredPaths = [
...requiredPluginPaths,
...((manifest.operations || []).length
? requiredCommandPluginPaths
: []),
...((manifest.events || []).length ? requiredEventPluginPaths : []),
];
return requiredPaths
.map((pathName) => `${pluginKey}/${pathName}`)
.filter((pathName) => !existsSync(join(pluginRoot, pathName)));
});
expect(missing).toEqual([]);
});
@ -128,6 +133,7 @@ describe('QQBot plugin package boundary', () => {
it('does not keep third-phase package directories as empty shells', () => {
const emptyRequiredDirs = [
'bangdream',
'bilibili-card',
'ff14-market',
'fflogs',
'repeater',
@ -242,6 +248,7 @@ describe('QQBot plugin package boundary', () => {
it('keeps plugin package entrypoints limited to createPlugin', () => {
const extraExports = [
'bangdream',
'bilibili-card',
'ff14-market',
'fflogs',
'repeater',
@ -319,7 +326,7 @@ describe('plugin platform package decoupling', () => {
.join('|'),
);
const forbiddenBranchPattern =
/pluginKey\s*(?:={2,3})\s*['"`](bangdream|ff14-market|fflogs|repeater)['"`]|case\s+['"`](bangdream|ff14-market|fflogs|repeater)['"`]/;
/pluginKey\s*(?:={2,3})\s*['"`](bangdream|bilibili-card|ff14-market|fflogs|repeater)['"`]|case\s+['"`](bangdream|bilibili-card|ff14-market|fflogs|repeater)['"`]/;
/**
* Escapes a literal token before it is placed into the architecture gate regexp.

View File

@ -41,7 +41,13 @@ describe('QQBot existing plugin platform migration', () => {
readdirSync(pluginRoot)
.filter((name) => statSync(join(pluginRoot, name)).isDirectory())
.sort(),
).toEqual(['bangdream', 'ff14-market', 'fflogs', 'repeater']);
).toEqual([
'bangdream',
'bilibili-card',
'ff14-market',
'fflogs',
'repeater',
]);
const legacySources = collectFiles(legacyPluginRoot).filter((filePath) =>
filePath.endsWith('.ts'),
@ -50,21 +56,26 @@ describe('QQBot existing plugin platform migration', () => {
});
it('declares parseable platform manifests for every existing plugin', () => {
const manifests = ['bangdream', 'ff14-market', 'fflogs', 'repeater'].map(
(pluginName) => {
const root = join(pluginRoot, pluginName);
const manifest = parseQqbotPluginManifest(
readJson(join(root, 'plugin.json')),
{
pluginRoot: root,
},
);
return manifest;
},
);
const manifests = [
'bangdream',
'bilibili-card',
'ff14-market',
'fflogs',
'repeater',
].map((pluginName) => {
const root = join(pluginRoot, pluginName);
const manifest = parseQqbotPluginManifest(
readJson(join(root, 'plugin.json')),
{
pluginRoot: root,
},
);
return manifest;
});
expect(manifests.map((manifest) => manifest.pluginKey).sort()).toEqual([
'bangdream',
'bilibili-card',
'ff14-market',
'fflogs',
'repeater',
@ -79,6 +90,16 @@ describe('QQBot existing plugin platform migration', () => {
).toBe(true);
});
it('seeds Bilibili card plugin event metadata in refactor v3 SQL', () => {
const refactorSeedSql = readFileSync(
join(repoRoot, 'sql/refactor-v3/01-seed-core.sql'),
'utf8',
);
expect(refactorSeedSql).toContain(`'bilibili-card'`);
expect(refactorSeedSql).toContain(`'bilibili-card.message'`);
});
it('keeps BangDream manifest operations as the single metadata source', () => {
const manifest = parseQqbotPluginManifest(
readJson(join(pluginRoot, 'bangdream/plugin.json')),