Channels: add contract surface coverage

This commit is contained in:
Vincent Koc
2026-03-16 01:32:11 -07:00
parent 5cd206f780
commit 429144d9f1
3 changed files with 298 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
import { describe, expect, it } from "vitest";
import {
actionContractRegistry,
pluginContractRegistry,
setupContractRegistry,
statusContractRegistry,
surfaceContractRegistry,
type ChannelPluginSurface,
} from "./registry.js";
const orderedSurfaceKeys = [
"actions",
"setup",
"status",
"outbound",
"messaging",
"threading",
"directory",
"gateway",
] as const satisfies readonly ChannelPluginSurface[];
describe("channel contract registry", () => {
it("does not duplicate channel plugin ids", () => {
const ids = pluginContractRegistry.map((entry) => entry.id);
expect(ids).toEqual([...new Set(ids)]);
});
it("keeps the surface registry aligned with the plugin registry", () => {
expect(surfaceContractRegistry.map((entry) => entry.id).toSorted()).toEqual(
pluginContractRegistry.map((entry) => entry.id).toSorted(),
);
});
it("declares the actual owned channel plugin surfaces explicitly", () => {
for (const entry of surfaceContractRegistry) {
const actual = orderedSurfaceKeys.filter((surface) => Boolean(entry.plugin[surface]));
expect([...entry.surfaces].toSorted()).toEqual(actual.toSorted());
}
});
it("only installs deep action coverage for plugins that declare actions", () => {
const actionSurfaceIds = new Set(
surfaceContractRegistry
.filter((entry) => entry.surfaces.includes("actions"))
.map((entry) => entry.id),
);
for (const entry of actionContractRegistry) {
expect(actionSurfaceIds.has(entry.id)).toBe(true);
}
});
it("only installs deep setup coverage for plugins that declare setup", () => {
const setupSurfaceIds = new Set(
surfaceContractRegistry
.filter((entry) => entry.surfaces.includes("setup"))
.map((entry) => entry.id),
);
for (const entry of setupContractRegistry) {
expect(setupSurfaceIds.has(entry.id)).toBe(true);
}
});
it("only installs deep status coverage for plugins that declare status", () => {
const statusSurfaceIds = new Set(
surfaceContractRegistry
.filter((entry) => entry.surfaces.includes("status"))
.map((entry) => entry.id),
);
for (const entry of statusContractRegistry) {
expect(statusSurfaceIds.has(entry.id)).toBe(true);
}
});
});

View File

@@ -57,6 +57,33 @@ type StatusContractEntry = {
}>;
};
export type ChannelPluginSurface =
| "actions"
| "setup"
| "status"
| "outbound"
| "messaging"
| "threading"
| "directory"
| "gateway";
type SurfaceContractEntry = {
id: string;
plugin: Pick<
ChannelPlugin,
| "id"
| "actions"
| "setup"
| "status"
| "outbound"
| "messaging"
| "threading"
| "directory"
| "gateway"
>;
surfaces: readonly ChannelPluginSurface[];
};
const telegramListActionsMock = vi.fn();
const telegramGetCapabilitiesMock = vi.fn();
const discordListActionsMock = vi.fn();
@@ -461,3 +488,187 @@ export const statusContractRegistry: StatusContractEntry[] = [
],
},
];
export const surfaceContractRegistry: SurfaceContractEntry[] = [
{
id: "bluebubbles",
plugin: bluebubblesPlugin,
surfaces: ["actions", "setup", "status", "outbound", "messaging", "threading", "gateway"],
},
{
id: "discord",
plugin: discordPlugin,
surfaces: [
"actions",
"setup",
"status",
"outbound",
"messaging",
"threading",
"directory",
"gateway",
],
},
{
id: "feishu",
plugin: feishuPlugin,
surfaces: ["actions", "setup", "status", "outbound", "messaging", "directory", "gateway"],
},
{
id: "googlechat",
plugin: googlechatPlugin,
surfaces: [
"actions",
"setup",
"status",
"outbound",
"messaging",
"threading",
"directory",
"gateway",
],
},
{
id: "imessage",
plugin: imessagePlugin,
surfaces: ["setup", "status", "outbound", "messaging", "gateway"],
},
{
id: "irc",
plugin: ircPlugin,
surfaces: ["setup", "status", "outbound", "messaging", "directory", "gateway"],
},
{
id: "line",
plugin: linePlugin,
surfaces: ["setup", "status", "outbound", "messaging", "directory", "gateway"],
},
{
id: "matrix",
plugin: matrixPlugin,
surfaces: [
"actions",
"setup",
"status",
"outbound",
"messaging",
"threading",
"directory",
"gateway",
],
},
{
id: "mattermost",
plugin: mattermostPlugin,
surfaces: [
"actions",
"setup",
"status",
"outbound",
"messaging",
"threading",
"directory",
"gateway",
],
},
{
id: "msteams",
plugin: msteamsPlugin,
surfaces: [
"actions",
"setup",
"status",
"outbound",
"messaging",
"threading",
"directory",
"gateway",
],
},
{
id: "nextcloud-talk",
plugin: nextcloudTalkPlugin,
surfaces: ["setup", "status", "outbound", "messaging", "gateway"],
},
{
id: "nostr",
plugin: nostrPlugin,
surfaces: ["setup", "status", "outbound", "messaging", "gateway"],
},
{
id: "signal",
plugin: signalPlugin,
surfaces: ["actions", "setup", "status", "outbound", "messaging", "gateway"],
},
{
id: "slack",
plugin: slackPlugin,
surfaces: [
"actions",
"setup",
"status",
"outbound",
"messaging",
"threading",
"directory",
"gateway",
],
},
{
id: "synology-chat",
plugin: synologyChatPlugin,
surfaces: ["setup", "outbound", "messaging", "directory", "gateway"],
},
{
id: "telegram",
plugin: telegramPlugin,
surfaces: [
"actions",
"setup",
"status",
"outbound",
"messaging",
"threading",
"directory",
"gateway",
],
},
{
id: "tlon",
plugin: tlonPlugin,
surfaces: ["setup", "status", "outbound", "messaging", "gateway"],
},
{
id: "whatsapp",
plugin: whatsappPlugin,
surfaces: ["actions", "setup", "status", "outbound", "messaging", "directory", "gateway"],
},
{
id: "zalo",
plugin: zaloPlugin,
surfaces: [
"actions",
"setup",
"status",
"outbound",
"messaging",
"threading",
"directory",
"gateway",
],
},
{
id: "zalouser",
plugin: zalouserPlugin,
surfaces: [
"actions",
"setup",
"status",
"outbound",
"messaging",
"threading",
"directory",
"gateway",
],
},
];

View File

@@ -0,0 +1,14 @@
import { describe } from "vitest";
import { surfaceContractRegistry } from "./registry.js";
import { installChannelSurfaceContractSuite } from "./suites.js";
for (const entry of surfaceContractRegistry) {
for (const surface of entry.surfaces) {
describe(`${entry.id} ${surface} surface contract`, () => {
installChannelSurfaceContractSuite({
plugin: entry.plugin,
surface,
});
});
}
}