Files
openclaw/extensions/msteams/src/conversation-store.shared.test.ts
Peter Steinberger 8fe6ac7fb9 refactor: remove pre-2026.4 compatibility shims and legacy migrations (#104650)
* refactor(matrix): retire pre-2026.4 legacy crypto and flat-storage migrations

* refactor(cli): drop pre-tsdown daemon-cli dist compat shim from the build

* refactor(diffs): remove deprecated image*/format tool params and output aliases

* refactor(ui): drop pre-gateway-scoped localStorage readers and legacy theme map

* refactor: remove assorted pre-2026.4 compat shims and deprecated aliases

* fix(discord): map legacy thread-binding fields in the doctor JSON import

* fix(msteams): keep bot read fallback for legacy imported conversation rows

* test(msteams): cover legacy bot-only imported conversation references

* fix(ui): keep channel-prefixed session key display fallback

* chore: refresh native i18n inventory and build-docker step count

* chore(matrix): drop now-unused migration-config module

* chore: re-pin plugin SDK public export budget after compat removals

* chore: refresh native i18n inventory after rebase

* chore: re-pin plugin SDK callable budget and refresh i18n inventory after rebase
2026-07-11 13:22:27 -07:00

266 lines
9.1 KiB
TypeScript

// Msteams tests cover conversation store.shared plugin behavior.
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
findPreferredDmConversationByUserId,
mergeStoredConversationReference,
normalizeStoredConversationId,
toConversationStoreEntries,
} from "./conversation-store-helpers.js";
import { createMSTeamsConversationStoreState } from "./conversation-store-state.js";
import type {
MSTeamsConversationStore,
MSTeamsConversationStoreEntry,
StoredConversationReference,
} from "./conversation-store.js";
import { setMSTeamsRuntime } from "./runtime.js";
import { msteamsRuntimeStub } from "./test-support/runtime.js";
type StoreFactory = {
name: string;
createStore: () => Promise<MSTeamsConversationStore>;
};
function createMemoryConversationStore(
initial: MSTeamsConversationStoreEntry[] = [],
): MSTeamsConversationStore {
const map = new Map<string, StoredConversationReference>();
for (const { conversationId, reference } of initial) {
map.set(normalizeStoredConversationId(conversationId), reference);
}
const findPreferredDmByUserId = async (
id: string,
): Promise<MSTeamsConversationStoreEntry | null> =>
findPreferredDmConversationByUserId(toConversationStoreEntries(map.entries()), id);
return {
upsert: async (conversationId, reference) => {
const normalizedId = normalizeStoredConversationId(conversationId);
map.set(
normalizedId,
mergeStoredConversationReference(
map.get(normalizedId),
reference,
new Date().toISOString(),
),
);
},
get: async (conversationId) => map.get(normalizeStoredConversationId(conversationId)) ?? null,
list: async () => toConversationStoreEntries(map.entries()),
remove: async (conversationId) => map.delete(normalizeStoredConversationId(conversationId)),
findPreferredDmByUserId,
};
}
const storeFactories: StoreFactory[] = [
{
name: "state",
createStore: async () => {
const stateDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "openclaw-msteams-store-"));
return createMSTeamsConversationStoreState({
env: { ...process.env, OPENCLAW_STATE_DIR: stateDir },
ttlMs: 60_000,
});
},
},
{
name: "memory",
createStore: async () => createMemoryConversationStore(),
},
];
describe.each(storeFactories)("msteams conversation store ($name)", ({ createStore }) => {
beforeEach(() => {
resetPluginStateStoreForTests();
setMSTeamsRuntime(msteamsRuntimeStub);
});
it("normalizes conversation ids consistently", async () => {
const store = await createStore();
await store.upsert("conv-norm;messageid=123", {
conversation: { id: "conv-norm" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "u1" },
});
const normalized = await store.get("conv-norm");
expect(normalized).toEqual({
conversation: { id: "conv-norm" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "u1" },
lastSeenAt: normalized?.lastSeenAt,
});
expect(typeof normalized?.lastSeenAt).toBe("string");
await expect(store.remove("conv-norm")).resolves.toBe(true);
await expect(store.get("conv-norm;messageid=123")).resolves.toBeNull();
});
it("upserts, lists, removes, and resolves users by both AAD and Bot Framework ids", async () => {
const store = await createStore();
vi.useFakeTimers();
try {
vi.setSystemTime(new Date("2026-03-25T20:00:00.000Z"));
await store.upsert("conv-a", {
conversation: { id: "conv-a" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "user-a", aadObjectId: "aad-a", name: "Alice" },
});
vi.setSystemTime(new Date("2026-03-25T20:00:30.000Z"));
await store.upsert("conv-b", {
conversation: { id: "conv-b" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "user-b", aadObjectId: "aad-b", name: "Bob" },
});
await expect(store.get("conv-a")).resolves.toEqual({
conversation: { id: "conv-a" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "user-a", aadObjectId: "aad-a", name: "Alice" },
lastSeenAt: "2026-03-25T20:00:00.000Z",
});
await expect(store.list()).resolves.toEqual([
{
conversationId: "conv-a",
reference: {
conversation: { id: "conv-a" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "user-a", aadObjectId: "aad-a", name: "Alice" },
lastSeenAt: "2026-03-25T20:00:00.000Z",
},
},
{
conversationId: "conv-b",
reference: {
conversation: { id: "conv-b" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "user-b", aadObjectId: "aad-b", name: "Bob" },
lastSeenAt: "2026-03-25T20:00:30.000Z",
},
},
]);
await expect(store.findPreferredDmByUserId(" aad-b ")).resolves.toEqual({
conversationId: "conv-b",
reference: {
conversation: { id: "conv-b" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "user-b", aadObjectId: "aad-b", name: "Bob" },
lastSeenAt: "2026-03-25T20:00:30.000Z",
},
});
await expect(store.findPreferredDmByUserId("user-a")).resolves.toEqual({
conversationId: "conv-a",
reference: {
conversation: { id: "conv-a" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "user-a", aadObjectId: "aad-a", name: "Alice" },
lastSeenAt: "2026-03-25T20:00:00.000Z",
},
});
await expect(store.findPreferredDmByUserId(" ")).resolves.toBeNull();
await expect(store.remove("conv-a")).resolves.toBe(true);
await expect(store.get("conv-a")).resolves.toBeNull();
await expect(store.remove("missing")).resolves.toBe(false);
} finally {
vi.useRealTimers();
}
});
it("preserves existing timezone when upsert omits timezone", async () => {
const store = await createStore();
vi.useFakeTimers();
try {
vi.setSystemTime(new Date("2026-03-25T20:00:00.000Z"));
await store.upsert("conv-tz", {
conversation: { id: "conv-tz" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "u1" },
timezone: "Europe/London",
});
vi.setSystemTime(new Date("2026-03-25T20:01:00.000Z"));
await store.upsert("conv-tz", {
conversation: { id: "conv-tz" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "u1" },
});
await expect(store.get("conv-tz")).resolves.toEqual({
conversation: { id: "conv-tz" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "u1" },
timezone: "Europe/London",
lastSeenAt: "2026-03-25T20:01:00.000Z",
});
} finally {
vi.useRealTimers();
}
});
it("prefers the freshest personal conversation for repeated upserts of the same user", async () => {
const store = await createStore();
vi.useFakeTimers();
try {
vi.setSystemTime(new Date("2026-03-25T20:00:00.000Z"));
await store.upsert("dm-old", {
conversation: { id: "dm-old", conversationType: "personal" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "user-shared-old", aadObjectId: "aad-shared", name: "Old DM" },
});
vi.setSystemTime(new Date("2026-03-25T20:30:00.000Z"));
await store.upsert("group-shared", {
conversation: { id: "group-shared", conversationType: "groupChat" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "user-shared-group", aadObjectId: "aad-shared", name: "Group" },
});
vi.setSystemTime(new Date("2026-03-25T21:00:00.000Z"));
await store.upsert("dm-new", {
conversation: { id: "dm-new", conversationType: "personal" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "user-shared-new", aadObjectId: "aad-shared", name: "New DM" },
});
await expect(store.findPreferredDmByUserId("aad-shared")).resolves.toEqual({
conversationId: "dm-new",
reference: {
conversation: { id: "dm-new", conversationType: "personal" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "user-shared-new", aadObjectId: "aad-shared", name: "New DM" },
lastSeenAt: "2026-03-25T21:00:00.000Z",
},
});
} finally {
vi.useRealTimers();
}
});
});