Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 160 additions & 35 deletions apps/web/utils/ai/assistant/chat-inbox-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,10 +706,156 @@ describe("chat inbox tools - bulk pagination guidance (INB-134)", () => {
expect(result.hasMore).toBe(false);
});

it("searchInbox retries Microsoft fielded sender searches with a plain-text fallback", async () => {
it("searchInbox uses exact Outlook sender filtering for fielded sender queries", async () => {
const searchMessages = vi.fn().mockResolvedValue({
messages: [
{
id: "m1",
threadId: "t1",
snippet: "Can you take a look?",
historyId: "",
inline: [],
headers: {
from: "sender@example.com",
to: TEST_EMAIL,
subject: "Review request",
date: "2026-01-01T00:00:00.000Z",
},
subject: "Review request",
textPlain: "",
textHtml: "",
labelIds: [],
internalDate: "0",
},
],
nextPageToken: undefined,
});

(createEmailProvider as any).mockResolvedValue({
searchMessages,
getLabels: vi.fn().mockResolvedValue([]),
});

const toolInstance = searchInboxTool({
email: TEST_EMAIL,
emailAccountId: "email-account-1",
provider: "microsoft",
logger,
});

const result: any = await (toolInstance.execute as any)({
query: "from:sender@example.com",
limit: 20,
});

expect(searchMessages).toHaveBeenCalledWith({
query: "",
fromEmail: "sender@example.com",
maxResults: 20,
pageToken: undefined,
readState: undefined,
labelName: undefined,
});
expect(result.messages).toHaveLength(1);
expect(result.queryUsed).toBe("from:sender@example.com");
});

it("searchInbox uses exact Outlook sender filtering for bare sender email queries", async () => {
const searchMessages = vi.fn().mockResolvedValue({
messages: [
{
id: "m1",
threadId: "t1",
snippet: "Can you take a look?",
historyId: "",
inline: [],
headers: {
from: "Sender <sender@example.com>",
to: TEST_EMAIL,
subject: "Review request",
date: "2026-01-01T00:00:00.000Z",
},
subject: "Review request",
textPlain: "",
textHtml: "",
labelIds: [],
internalDate: "0",
},
],
nextPageToken: "PAGE_TOKEN_2",
});

(createEmailProvider as any).mockResolvedValue({
searchMessages,
getLabels: vi.fn().mockResolvedValue([]),
});

const toolInstance = searchInboxTool({
email: TEST_EMAIL,
emailAccountId: "email-account-1",
provider: "microsoft",
logger,
});

const result: any = await (toolInstance.execute as any)({
query: "sender@example.com",
limit: 20,
});

expect(searchMessages).toHaveBeenCalledWith({
query: "",
fromEmail: "sender@example.com",
maxResults: 20,
pageToken: undefined,
readState: undefined,
labelName: undefined,
});
expect(result.queryUsed).toBe("from:sender@example.com");
expect(result.nextPageToken).toBe("PAGE_TOKEN_2");
expect(result.hasMore).toBe(true);
});

it("searchInbox forwards explicit Outlook sender filters across pages", async () => {
const searchMessages = vi.fn().mockResolvedValue({
messages: [],
nextPageToken: undefined,
});

(createEmailProvider as any).mockResolvedValue({
searchMessages,
getLabels: vi.fn().mockResolvedValue([]),
});

const toolInstance = searchInboxTool({
email: TEST_EMAIL,
emailAccountId: "email-account-1",
provider: "microsoft",
logger,
});

await (toolInstance.execute as any)({
fromEmail: "sender@example.com",
limit: 20,
pageToken: "PAGE_TOKEN_2",
});

expect(searchMessages).toHaveBeenCalledWith({
query: "",
fromEmail: "sender@example.com",
maxResults: 20,
pageToken: "PAGE_TOKEN_2",
readState: undefined,
labelName: undefined,
});
});

it("searchInbox preserves structured Outlook sender filters when skipping empty pages", async () => {
const searchMessages = vi
.fn()
.mockRejectedValueOnce(new Error("Search syntax failed"))
.mockResolvedValueOnce({
messages: [],
nextPageToken: "PAGE_TOKEN_2",
})
.mockResolvedValueOnce({
messages: [
{
Expand All @@ -719,7 +865,7 @@ describe("chat inbox tools - bulk pagination guidance (INB-134)", () => {
historyId: "",
inline: [],
headers: {
from: "sender@example.com",
from: "Sender <sender@example.com>",
to: TEST_EMAIL,
subject: "Review request",
date: "2026-01-01T00:00:00.000Z",
Expand Down Expand Up @@ -752,21 +898,23 @@ describe("chat inbox tools - bulk pagination guidance (INB-134)", () => {
});

expect(searchMessages).toHaveBeenNthCalledWith(1, {
query: "from:sender@example.com",
query: "",
fromEmail: "sender@example.com",
maxResults: 20,
pageToken: undefined,
readState: undefined,
labelName: undefined,
});
expect(searchMessages).toHaveBeenNthCalledWith(2, {
query: '"sender@example.com"',
query: "",
fromEmail: "sender@example.com",
maxResults: 20,
pageToken: undefined,
pageToken: "PAGE_TOKEN_2",
readState: undefined,
labelName: undefined,
});
expect(result.messages).toHaveLength(1);
expect(result.queryUsed).toBe('"sender@example.com"');
expect(result.queryUsed).toBe("from:sender@example.com");
});

it("searchInbox passes structured Outlook category and read-state filters", async () => {
Expand Down Expand Up @@ -1138,45 +1286,22 @@ describe("chat inbox tools - bulk pagination guidance (INB-134)", () => {
});

const result: any = await (toolInstance.execute as any)({
query: "from:sender@example.com",
query: 'from:sender@example.com subject:"weekly report"',
limit: 20,
});

expect(result).toMatchObject({
queryUsed: "from:sender@example.com",
error: "Failed to search inbox",
provider: "microsoft",
microsoftSearchFeedback: {
failureType: "query_failed",
summary:
"Outlook did not return results for the attempted search query. Retry with one simpler Outlook clause at a time.",
fallbackAttempted: true,
likelyCause: "Retry with one simpler Outlook clause at a time.",
removedTerms: [],
retryQueries: [],
},
});
expect(result.microsoftSearchFeedback.attempts).toEqual([
{
query: "from:sender@example.com",
status: 400,
code: "BadRequest",
message: "Unsupported search clause",
},
{
query: '"sender@example.com"',
status: 400,
code: "BadRequest",
message: "Unsupported search clause",
},
{
query: "sender@example.com",
status: 400,
code: "BadRequest",
message: "Unsupported search clause",
},
]);
expect(searchMessages).toHaveBeenCalledTimes(3);
expect(result.microsoftSearchFeedback.attempts.length).toBeGreaterThan(1);
expect(searchMessages).toHaveBeenCalledTimes(
result.microsoftSearchFeedback.attempts.length,
);
});

it("searchInbox suggests concrete simpler retries for complex Microsoft queries", async () => {
Expand Down
Loading
Loading