mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-23 16:01:17 +00:00
test: tighten msteams regression assertions
This commit is contained in:
@@ -35,7 +35,10 @@ describe("msteams inbound", () => {
|
||||
|
||||
it("parses string timestamps", () => {
|
||||
const ts = parseMSTeamsActivityTimestamp("2024-01-01T00:00:00.000Z");
|
||||
expect(ts?.toISOString()).toBe("2024-01-01T00:00:00.000Z");
|
||||
if (!ts) {
|
||||
throw new Error("expected MSTeams timestamp parser to return a Date");
|
||||
}
|
||||
expect(ts.toISOString()).toBe("2024-01-01T00:00:00.000Z");
|
||||
});
|
||||
|
||||
it("passes through Date instances", () => {
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildMentionEntities, formatMentionText, parseMentions } from "./mentions.js";
|
||||
|
||||
function requireFirstEntity(result: ReturnType<typeof parseMentions>) {
|
||||
const entity = result.entities[0];
|
||||
if (!entity) {
|
||||
throw new Error("expected parseMentions to return at least one entity");
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
describe("parseMentions", () => {
|
||||
it("parses single mention", () => {
|
||||
const result = parseMentions("Hello @[John Doe](28:a1b2c3-d4e5f6)!");
|
||||
@@ -58,7 +66,7 @@ describe("parseMentions", () => {
|
||||
const result = parseMentions("@[John Peter Smith](28:a1b2c3)");
|
||||
|
||||
expect(result.text).toBe("<at>John Peter Smith</at>");
|
||||
expect(result.entities[0]?.mentioned.name).toBe("John Peter Smith");
|
||||
expect(requireFirstEntity(result).mentioned.name).toBe("John Peter Smith");
|
||||
});
|
||||
|
||||
it("trims whitespace from id and name", () => {
|
||||
@@ -90,7 +98,7 @@ describe("parseMentions", () => {
|
||||
});
|
||||
|
||||
// Verify entity text exactly matches what's in the formatted text
|
||||
const entityText = result.entities[0]?.text;
|
||||
const entityText = requireFirstEntity(result).text;
|
||||
expect(result.text).toContain(entityText);
|
||||
expect(result.text.indexOf(entityText)).toBe(0);
|
||||
});
|
||||
@@ -108,8 +116,9 @@ describe("parseMentions", () => {
|
||||
|
||||
// Only the real mention should be parsed; the documentation example should be left as-is
|
||||
expect(result.entities).toHaveLength(1);
|
||||
expect(result.entities[0]?.mentioned.id).toBe("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
|
||||
expect(result.entities[0]?.mentioned.name).toBe("タナカ タロウ");
|
||||
const firstEntity = requireFirstEntity(result);
|
||||
expect(firstEntity.mentioned.id).toBe("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
|
||||
expect(firstEntity.mentioned.name).toBe("タナカ タロウ");
|
||||
|
||||
// The documentation pattern must remain untouched in the text
|
||||
expect(result.text).toContain("`@[表示名](ユーザーID)`");
|
||||
@@ -118,25 +127,27 @@ describe("parseMentions", () => {
|
||||
it("accepts Bot Framework IDs (28:xxx)", () => {
|
||||
const result = parseMentions("@[Bot](28:abc-123)");
|
||||
expect(result.entities).toHaveLength(1);
|
||||
expect(result.entities[0]?.mentioned.id).toBe("28:abc-123");
|
||||
expect(requireFirstEntity(result).mentioned.id).toBe("28:abc-123");
|
||||
});
|
||||
|
||||
it("accepts Bot Framework IDs with non-hex payloads (29:xxx)", () => {
|
||||
const result = parseMentions("@[Bot](29:08q2j2o3jc09au90eucae)");
|
||||
expect(result.entities).toHaveLength(1);
|
||||
expect(result.entities[0]?.mentioned.id).toBe("29:08q2j2o3jc09au90eucae");
|
||||
expect(requireFirstEntity(result).mentioned.id).toBe("29:08q2j2o3jc09au90eucae");
|
||||
});
|
||||
|
||||
it("accepts org-scoped IDs with extra segments (8:orgid:...)", () => {
|
||||
const result = parseMentions("@[User](8:orgid:2d8c2d2c-1111-2222-3333-444444444444)");
|
||||
expect(result.entities).toHaveLength(1);
|
||||
expect(result.entities[0]?.mentioned.id).toBe("8:orgid:2d8c2d2c-1111-2222-3333-444444444444");
|
||||
expect(requireFirstEntity(result).mentioned.id).toBe(
|
||||
"8:orgid:2d8c2d2c-1111-2222-3333-444444444444",
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts AAD object IDs (UUIDs)", () => {
|
||||
const result = parseMentions("@[User](a1b2c3d4-e5f6-7890-abcd-ef1234567890)");
|
||||
expect(result.entities).toHaveLength(1);
|
||||
expect(result.entities[0]?.mentioned.id).toBe("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
|
||||
expect(requireFirstEntity(result).mentioned.id).toBe("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
|
||||
});
|
||||
|
||||
it("rejects non-ID strings as mention targets", () => {
|
||||
|
||||
@@ -222,7 +222,10 @@ describe("msteams messenger", () => {
|
||||
conversation?: { id?: string };
|
||||
};
|
||||
expect(ref.activityId).toBeUndefined();
|
||||
expect(ref.conversation?.id).toBe("19:abc@thread.tacv2");
|
||||
if (!ref.conversation?.id) {
|
||||
throw new Error("expected Teams top-level send to preserve conversation id");
|
||||
}
|
||||
expect(ref.conversation.id).toBe("19:abc@thread.tacv2");
|
||||
});
|
||||
|
||||
it("preserves parsed mentions when appending OneDrive fallback file links", async () => {
|
||||
@@ -262,11 +265,15 @@ describe("msteams messenger", () => {
|
||||
expect(ids).toEqual(["id:one"]);
|
||||
expect(graphUploadMockState.uploadAndShareOneDrive).toHaveBeenCalledOnce();
|
||||
expect(sent).toHaveLength(1);
|
||||
expect(sent[0]?.text).toContain("Hello <at>John</at>");
|
||||
expect(sent[0]?.text).toContain(
|
||||
const firstSent = sent[0];
|
||||
if (!firstSent?.text) {
|
||||
throw new Error("expected Teams message send to include rendered text");
|
||||
}
|
||||
expect(firstSent.text).toContain("Hello <at>John</at>");
|
||||
expect(firstSent.text).toContain(
|
||||
"📎 [upload.txt](https://onedrive.example.com/share/item123)",
|
||||
);
|
||||
expect(sent[0]?.entities).toEqual([
|
||||
expect(firstSent.entities).toEqual([
|
||||
{
|
||||
type: "mention",
|
||||
text: "<at>John</at>",
|
||||
|
||||
@@ -147,6 +147,14 @@ function createConsentInvokeHarness(params: {
|
||||
return { uploadId, handler, context, sendActivity };
|
||||
}
|
||||
|
||||
function requirePendingUpload(uploadId: string) {
|
||||
const upload = getPendingUpload(uploadId);
|
||||
if (!upload) {
|
||||
throw new Error(`expected pending upload ${uploadId}`);
|
||||
}
|
||||
return upload;
|
||||
}
|
||||
|
||||
describe("msteams file consent invoke authz", () => {
|
||||
beforeEach(() => {
|
||||
setMSTeamsRuntime(runtimeStub);
|
||||
@@ -161,7 +169,7 @@ describe("msteams file consent invoke authz", () => {
|
||||
action: "accept",
|
||||
});
|
||||
|
||||
await handler.run?.(context);
|
||||
await handler.run(context);
|
||||
|
||||
// invokeResponse should be sent immediately
|
||||
expect(sendActivity).toHaveBeenCalledWith(
|
||||
@@ -186,7 +194,7 @@ describe("msteams file consent invoke authz", () => {
|
||||
action: "accept",
|
||||
});
|
||||
|
||||
await handler.run?.(context);
|
||||
await handler.run(context);
|
||||
|
||||
// invokeResponse should be sent immediately
|
||||
expect(sendActivity).toHaveBeenCalledWith(
|
||||
@@ -200,7 +208,11 @@ describe("msteams file consent invoke authz", () => {
|
||||
);
|
||||
|
||||
expect(fileConsentMockState.uploadToConsentUrl).not.toHaveBeenCalled();
|
||||
expect(getPendingUpload(uploadId)).toBeDefined();
|
||||
expect(requirePendingUpload(uploadId)).toMatchObject({
|
||||
conversationId: "19:victim@thread.v2",
|
||||
filename: "secret.txt",
|
||||
contentType: "text/plain",
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores cross-conversation decline invoke and keeps pending upload", async () => {
|
||||
@@ -209,7 +221,7 @@ describe("msteams file consent invoke authz", () => {
|
||||
action: "decline",
|
||||
});
|
||||
|
||||
await handler.run?.(context);
|
||||
await handler.run(context);
|
||||
|
||||
// invokeResponse should be sent immediately
|
||||
expect(sendActivity).toHaveBeenCalledWith(
|
||||
@@ -219,7 +231,11 @@ describe("msteams file consent invoke authz", () => {
|
||||
);
|
||||
|
||||
expect(fileConsentMockState.uploadToConsentUrl).not.toHaveBeenCalled();
|
||||
expect(getPendingUpload(uploadId)).toBeDefined();
|
||||
expect(requirePendingUpload(uploadId)).toMatchObject({
|
||||
conversationId: "19:victim@thread.v2",
|
||||
filename: "secret.txt",
|
||||
contentType: "text/plain",
|
||||
});
|
||||
expect(sendActivity).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,8 +47,11 @@ describe("msteams policy", () => {
|
||||
conversationId: "chan456",
|
||||
});
|
||||
|
||||
expect(res.teamConfig?.requireMention).toBe(false);
|
||||
expect(res.channelConfig?.requireMention).toBe(true);
|
||||
if (!res.teamConfig || !res.channelConfig) {
|
||||
throw new Error("expected matched team and channel config");
|
||||
}
|
||||
expect(res.teamConfig.requireMention).toBe(false);
|
||||
expect(res.channelConfig.requireMention).toBe(true);
|
||||
expect(res.allowlistConfigured).toBe(true);
|
||||
expect(res.allowed).toBe(true);
|
||||
expect(res.channelMatchKey).toBe("chan456");
|
||||
@@ -82,8 +85,11 @@ describe("msteams policy", () => {
|
||||
it("matches team and channel by name when dangerous name matching is enabled", () => {
|
||||
const res = resolveNamedTeamRouteConfig(true);
|
||||
|
||||
expect(res.teamConfig?.requireMention).toBe(true);
|
||||
expect(res.channelConfig?.requireMention).toBe(false);
|
||||
if (!res.teamConfig || !res.channelConfig) {
|
||||
throw new Error("expected matched named team and channel config");
|
||||
}
|
||||
expect(res.teamConfig.requireMention).toBe(true);
|
||||
expect(res.channelConfig.requireMention).toBe(false);
|
||||
expect(res.allowed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,9 @@ describe.each([
|
||||
selections: ["0", "1"],
|
||||
});
|
||||
|
||||
expect(poll?.votes["user-1"]).toEqual(["0"]);
|
||||
if (!poll) {
|
||||
throw new Error("poll store did not return the updated poll");
|
||||
}
|
||||
expect(poll.votes["user-1"]).toEqual(["0"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,9 @@ describe("msteams polls", () => {
|
||||
options: ["Pizza", "Sushi"],
|
||||
});
|
||||
|
||||
expect(card.pollId).toBeTruthy();
|
||||
expect(card.pollId).toMatch(
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
||||
);
|
||||
expect(card.fallbackText).toContain("Poll: Lunch?");
|
||||
expect(card.fallbackText).toContain("1. Pizza");
|
||||
expect(card.fallbackText).toContain("2. Sushi");
|
||||
@@ -54,6 +56,9 @@ describe("msteams polls", () => {
|
||||
selections: ["0", "1"],
|
||||
});
|
||||
const stored = await store.getPoll("poll-2");
|
||||
expect(stored?.votes["user-1"]).toEqual(["0"]);
|
||||
if (!stored) {
|
||||
throw new Error("expected stored poll after recordVote");
|
||||
}
|
||||
expect(stored.votes["user-1"]).toEqual(["0"]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user