feat(chat): handle credit-gated send responses
This commit is contained in:
@@ -38,6 +38,12 @@ function makeChatState(overrides: Partial<ChatState> = {}): ChatState {
|
||||
pendingReplyCount: 1,
|
||||
upgradePromptVisible: false,
|
||||
upgradeReason: null,
|
||||
canSendMessage: true,
|
||||
cannotSendReason: null,
|
||||
creditBalance: 0,
|
||||
creditsCharged: 0,
|
||||
requiredCredits: 0,
|
||||
shortfallCredits: 0,
|
||||
isLoadingMore: false,
|
||||
hasMore: true,
|
||||
historyOffset: 0,
|
||||
@@ -168,6 +174,96 @@ describe("applyHttpSendOutput", () => {
|
||||
expect(nextState.upgradePromptVisible).toBe(true);
|
||||
expect(nextState.upgradeReason).toBe("weekly_limit");
|
||||
});
|
||||
|
||||
it("stores insufficient credit state and removes failed optimistic messages", () => {
|
||||
const context = makeChatState({
|
||||
messages: [
|
||||
{
|
||||
content: "hello",
|
||||
isFromAI: false,
|
||||
date: "2026-06-25",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const nextState = applyHttpSendOutput(context, {
|
||||
response: makeResponse({
|
||||
reply: "",
|
||||
messageId: "",
|
||||
canSendMessage: false,
|
||||
cannotSendReason: "insufficient_credits",
|
||||
creditBalance: 0,
|
||||
creditsCharged: 0,
|
||||
requiredCredits: 2,
|
||||
shortfallCredits: 2,
|
||||
lockDetail: {
|
||||
locked: true,
|
||||
showContent: false,
|
||||
showUpgrade: true,
|
||||
reason: "insufficient_credits",
|
||||
hint: "Insufficient credits.",
|
||||
detail: {
|
||||
requiredCredits: 2,
|
||||
currentCredits: 0,
|
||||
shortfallCredits: 2,
|
||||
},
|
||||
},
|
||||
}),
|
||||
reply: null,
|
||||
});
|
||||
|
||||
expect(nextState.messages).toEqual([]);
|
||||
expect(nextState.isReplyingAI).toBe(false);
|
||||
expect(nextState.upgradePromptVisible).toBe(true);
|
||||
expect(nextState.upgradeReason).toBe("insufficient_credits");
|
||||
expect(nextState.canSendMessage).toBe(false);
|
||||
expect(nextState.cannotSendReason).toBe("insufficient_credits");
|
||||
expect(nextState.requiredCredits).toBe(2);
|
||||
expect(nextState.shortfallCredits).toBe(2);
|
||||
});
|
||||
|
||||
it("keeps a valid reply while marking future sends as unavailable", () => {
|
||||
const context = makeChatState({
|
||||
messages: [
|
||||
{
|
||||
content: "hello",
|
||||
isFromAI: false,
|
||||
date: "2026-06-25",
|
||||
},
|
||||
],
|
||||
});
|
||||
const reply = sendResponseToUiMessage(
|
||||
makeResponse({
|
||||
reply: "This one went through, but you need credits next.",
|
||||
canSendMessage: false,
|
||||
cannotSendReason: "insufficient_credits",
|
||||
creditBalance: 0,
|
||||
requiredCredits: 2,
|
||||
shortfallCredits: 2,
|
||||
}),
|
||||
);
|
||||
|
||||
const nextState = applyHttpSendOutput(context, {
|
||||
response: makeResponse({
|
||||
reply: "This one went through, but you need credits next.",
|
||||
canSendMessage: false,
|
||||
cannotSendReason: "insufficient_credits",
|
||||
creditBalance: 0,
|
||||
requiredCredits: 2,
|
||||
shortfallCredits: 2,
|
||||
}),
|
||||
reply,
|
||||
});
|
||||
|
||||
expect(nextState.messages).toHaveLength(2);
|
||||
expect(nextState.messages?.[1]).toMatchObject({
|
||||
content: "This one went through, but you need credits next.",
|
||||
isFromAI: true,
|
||||
});
|
||||
expect(nextState.upgradePromptVisible).toBe(true);
|
||||
expect(nextState.upgradeReason).toBe("insufficient_credits");
|
||||
expect(nextState.canSendMessage).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("localMessagesToUi", () => {
|
||||
|
||||
@@ -467,6 +467,11 @@ describe("chatMachine transitions", () => {
|
||||
limit: 3,
|
||||
},
|
||||
},
|
||||
canSendMessage: false,
|
||||
cannotSendReason: "insufficient_credits",
|
||||
creditBalance: 0,
|
||||
requiredCredits: 2,
|
||||
shortfallCredits: 2,
|
||||
}),
|
||||
reply: null,
|
||||
},
|
||||
@@ -474,11 +479,14 @@ describe("chatMachine transitions", () => {
|
||||
|
||||
expect(actor.getSnapshot().context.upgradePromptVisible).toBe(true);
|
||||
expect(actor.getSnapshot().context.upgradeReason).toBe("weekly_limit");
|
||||
expect(actor.getSnapshot().context.canSendMessage).toBe(false);
|
||||
|
||||
actor.send({ type: "ChatPaymentSucceeded" });
|
||||
|
||||
expect(actor.getSnapshot().context.upgradePromptVisible).toBe(false);
|
||||
expect(actor.getSnapshot().context.upgradeReason).toBeNull();
|
||||
expect(actor.getSnapshot().context.canSendMessage).toBe(true);
|
||||
expect(actor.getSnapshot().context.cannotSendReason).toBeNull();
|
||||
|
||||
actor.stop();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user