feat(chat): unlock history after payment

This commit is contained in:
2026-06-29 10:53:52 +08:00
parent 58cd2a7545
commit b7779878cf
15 changed files with 591 additions and 18 deletions
@@ -28,6 +28,15 @@ interface SendMessageHttpOutput {
reply: UiMessage | null;
}
interface UnlockHistoryOutput {
unlocked: boolean;
reason: string;
shortfallCredits: number;
messages: UiMessage[];
hasMore: boolean;
newOffset: number;
}
function makeChatSendResponse(): ChatSendResponse {
return ChatSendResponse.from({
reply: "",
@@ -50,6 +59,7 @@ function makeChatSendResponse(): ChatSendResponse {
function createTestChatMachine(
options: {
historyMessages?: UiMessage[];
unlockHistoryOutput?: UnlockHistoryOutput;
} = {},
) {
return chatMachine.provide({
@@ -90,6 +100,15 @@ function createTestChatMachine(
});
return () => undefined;
}),
unlockHistory: fromPromise<UnlockHistoryOutput>(async () => ({
unlocked: true,
reason: "ok",
shortfallCredits: 0,
messages: [],
hasMore: false,
newOffset: 0,
...options.unlockHistoryOutput,
})),
},
});
}
@@ -213,6 +232,14 @@ describe("chatMachine transitions", () => {
});
return () => undefined;
}),
unlockHistory: fromPromise<UnlockHistoryOutput>(async () => ({
unlocked: true,
reason: "ok",
shortfallCredits: 0,
messages: [],
hasMore: false,
newOffset: 0,
})),
},
});
const actor = createActor(machine).start();
@@ -235,4 +262,96 @@ describe("chatMachine transitions", () => {
actor.stop();
});
it("prompts before unlocking multiple locked history messages after payment", async () => {
const actor = createActor(
createTestChatMachine({
historyMessages: [
{
content: "",
isFromAI: true,
date: "2026-06-29",
locked: true,
lockReason: "private_message",
},
{
content: "",
isFromAI: true,
date: "2026-06-29",
locked: true,
lockReason: "voice_message",
},
],
}),
).start();
actor.send({ type: "ChatUserLogin", token: "token" });
await waitFor(actor, (snapshot) =>
snapshot.matches({ userSession: "ready" }),
);
actor.send({ type: "ChatPaymentSucceeded" });
expect(actor.getSnapshot().context.unlockHistoryPromptVisible).toBe(true);
expect(actor.getSnapshot().context.lockedHistoryCount).toBe(2);
actor.stop();
});
it("unlocks history after the prompt is confirmed", async () => {
const actor = createActor(
createTestChatMachine({
historyMessages: [
{
content: "",
isFromAI: true,
date: "2026-06-29",
locked: true,
lockReason: "private_message",
},
{
content: "",
isFromAI: true,
date: "2026-06-29",
locked: true,
lockReason: "voice_message",
},
],
unlockHistoryOutput: {
unlocked: true,
reason: "ok",
shortfallCredits: 0,
messages: [
{
content: "unlocked",
isFromAI: true,
date: "2026-06-29",
locked: false,
lockReason: null,
},
],
hasMore: false,
newOffset: 1,
},
}),
).start();
actor.send({ type: "ChatUserLogin", token: "token" });
await waitFor(actor, (snapshot) =>
snapshot.matches({ userSession: "ready" }),
);
actor.send({ type: "ChatPaymentSucceeded" });
actor.send({ type: "ChatUnlockHistoryConfirmed" });
await waitFor(actor, (snapshot) =>
snapshot.matches({ userSession: "ready" }),
);
expect(actor.getSnapshot().context.unlockHistoryPromptVisible).toBe(false);
expect(actor.getSnapshot().context.messages).toMatchObject([
{ content: "unlocked", locked: false },
]);
actor.stop();
});
});