Skip to content
Merged
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
54 changes: 54 additions & 0 deletions apps/web/src/approval-actions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { afterEach, describe, expect, test } from "bun:test";
import { QueryClient } from "@tanstack/react-query";
import { CHAT_STRINGS } from "@corbits/chat-ui";

import { createChatApprovalActions } from "./approval-actions";

describe("createChatApprovalActions copy", () => {
const realFetch = globalThis.fetch;

afterEach(() => {
globalThis.fetch = realFetch;
});

function stubStatus(status: number): void {
globalThis.fetch = (() =>
Promise.resolve(
new Response(JSON.stringify({ message: "nope" }), {
status,
headers: { "content-type": "application/json" },
}),
)) as unknown as typeof fetch;
}

function actions() {
return createChatApprovalActions(
"tenant-1",
new QueryClient({ defaultOptions: { queries: { retry: false } } }),
);
}

test("approve 403 uses approve forbidden copy, not deny", async () => {
stubStatus(403);
const result = await actions().approve("appr_1");
expect(result).toEqual({
kind: "forbidden",
message: CHAT_STRINGS.blockApproveActionForbidden,
});
expect(CHAT_STRINGS.blockApproveActionForbidden).not.toBe(
CHAT_STRINGS.blockDenyActionForbidden,
);
});

test("reject 403 uses deny forbidden copy, not approve", async () => {
stubStatus(403);
const result = await actions().reject("appr_1");
expect(result).toEqual({
kind: "forbidden",
message: CHAT_STRINGS.blockDenyActionForbidden,
});
expect(CHAT_STRINGS.blockDenyActionForbidden).not.toBe(
CHAT_STRINGS.blockApproveActionForbidden,
);
});
});
9 changes: 5 additions & 4 deletions apps/web/src/approval-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import type { QueryClient } from "@tanstack/react-query";
import type { ApprovalActions, ApprovalDecisionResult } from "@corbits/chat-ui";
import { CHAT_STRINGS } from "@corbits/chat-ui";
import { ApiQueryError } from "@corbits/api-query";

import { approveApproval, getApprovalNeedsYou, rejectApproval } from "./api";
Expand Down Expand Up @@ -108,16 +109,16 @@ export function createChatApprovalActions(
return resolve(
() => approveApproval(tenantId, approvalId),
"approved",
"You do not have permission to approve this.",
"Couldn't approve this request.",
CHAT_STRINGS.blockApproveActionForbidden,
CHAT_STRINGS.blockApproveActionError,
);
},
reject(approvalId) {
return resolve(
() => rejectApproval(tenantId, approvalId),
"rejected",
"You do not have permission to deny this.",
"Couldn't deny this request.",
CHAT_STRINGS.blockDenyActionForbidden,
CHAT_STRINGS.blockDenyActionError,
);
},
};
Expand Down
55 changes: 55 additions & 0 deletions apps/web/src/block-response-actions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { afterEach, describe, expect, test } from "bun:test";
import { CHAT_STRINGS } from "@corbits/chat-ui";

import { createChatBlockResponseActions } from "./block-response-actions";

describe("createChatBlockResponseActions copy", () => {
const realFetch = globalThis.fetch;

afterEach(() => {
globalThis.fetch = realFetch;
});

function stubStatus(status: number): void {
globalThis.fetch = (() =>
Promise.resolve(
new Response(JSON.stringify({ message: "nope" }), {
status,
headers: { "content-type": "application/json" },
}),
)) as unknown as typeof fetch;
}

function actions() {
return createChatBlockResponseActions("tenant-1", "wb_1");
}

test("form 403 uses form-submit forbidden, not approve copy", async () => {
stubStatus(403);
const result = await actions().submitForm("msg_1", "form_1", {
name: "Ada",
});
expect(result).toEqual({
kind: "forbidden",
message: CHAT_STRINGS.blockFormSubmitForbidden,
});
expect(CHAT_STRINGS.blockFormSubmitForbidden).not.toBe(
CHAT_STRINGS.blockApproveActionForbidden,
);
});

test("poll and question 403 share form-submit forbidden copy", async () => {
stubStatus(403);
const ports = actions();
const poll = await ports.submitPoll("msg_1", "poll_1", ["choice_1"]);
const question = await ports.submitQuestion("msg_1", "q_1", "yes", 0);
expect(poll).toEqual({
kind: "forbidden",
message: CHAT_STRINGS.blockFormSubmitForbidden,
});
expect(question).toEqual({
kind: "forbidden",
message: CHAT_STRINGS.blockFormSubmitForbidden,
});
});
});
9 changes: 6 additions & 3 deletions apps/web/src/block-response-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
BlockResponseSubmitResult,
} from "@corbits/chat-ui";
import {
CHAT_STRINGS,
ChatApiError,
getBlockResponses,
submitFormResponse,
Expand All @@ -31,13 +32,15 @@ export function createChatBlockResponseActions(
if (cause instanceof ChatApiError && cause.status === 403) {
return {
kind: "forbidden",
message:
"You do not have permission to respond in this conversation.",
message: CHAT_STRINGS.blockFormSubmitForbidden,
};
}
return {
kind: "error",
message: cause instanceof Error ? cause.message : "Couldn't submit.",
message:
cause instanceof Error
? cause.message
: CHAT_STRINGS.blockFormSubmitError,
};
}
}
Expand Down
26 changes: 26 additions & 0 deletions packages/chat-ui/src/strings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,29 @@ describe("agentsTyping copy", () => {
expect(CHAT_STRINGS.agentsTyping([])).toBe("");
});
});

describe("approve vs deny vs form action copy", () => {
test("deny forbidden and error are distinct from approve copy", () => {
expect(CHAT_STRINGS.blockDenyActionForbidden).toBe(
"You do not have permission to deny this.",
);
expect(CHAT_STRINGS.blockDenyActionError).toBe(
"Couldn't deny this request.",
);
expect(CHAT_STRINGS.blockDenyActionForbidden).not.toBe(
CHAT_STRINGS.blockApproveActionForbidden,
);
expect(CHAT_STRINGS.blockDenyActionError).not.toBe(
CHAT_STRINGS.blockApproveActionError,
);
});

test("form submit forbidden is respond copy, not approve copy", () => {
expect(CHAT_STRINGS.blockFormSubmitForbidden).toBe(
"You do not have permission to respond in this conversation.",
);
expect(CHAT_STRINGS.blockFormSubmitForbidden).not.toBe(
CHAT_STRINGS.blockApproveActionForbidden,
);
});
});
4 changes: 4 additions & 0 deletions packages/chat-ui/src/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ export const CHAT_STRINGS = {
blockApproveRejecting: "Denying…",
blockApproveActionForbidden: "You do not have permission to act on this.",
blockApproveActionError: "Couldn't reach the approval — try again.",
blockDenyActionForbidden: "You do not have permission to deny this.",
blockDenyActionError: "Couldn't deny this request.",
blockApprovePlatformRequestedBy: (agentName: string) =>
`${agentName} is asking to`,
blockApproveAgentNoteLabel: "Agent's note",
Expand All @@ -184,6 +186,8 @@ export const CHAT_STRINGS = {
blockFormSubmitted: "Submitted",
blockFormEdit: "Edit response",
blockFormSubmitError: "Couldn't submit — try again.",
blockFormSubmitForbidden:
"You do not have permission to respond in this conversation.",
blockFormFieldRequired: "This field is required.",
blockQuestionFreeTextLabel: "Type your own answer",
blockQuestionFreeTextPlaceholder: "Type your own answer…",
Expand Down
Loading