@alexkroman1/aai-cli 5.14.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{_agent-C8Jk-ZcP.mjs → _agent-vGW5SBnX.mjs} +23 -5
- package/dist/_agent.d.ts +18 -0
- package/dist/{_api-client-BX8Qv51n.mjs → _api-client-LgLksMhN.mjs} +45 -8
- package/dist/_api-client.d.ts +39 -0
- package/dist/{_bundler-BuIyDdvs.mjs → _bundler-BapCUUJN.mjs} +42 -14
- package/dist/_bundler.d.ts +43 -5
- package/dist/_cli-common.d.ts +67 -3
- package/dist/{_config-CzwAWOWr.mjs → _config-D_s09e7g.mjs} +36 -18
- package/dist/_deploy.d.ts +2 -5
- package/dist/_dev-restart.d.ts +6 -1
- package/dist/{_dev-server-Ch7K8e8i.mjs → _dev-server-DQ1t9sbw.mjs} +94 -38
- package/dist/_dev-server.d.ts +30 -2
- package/dist/_e2e-test-utils.d.ts +80 -0
- package/dist/_fault-mode.d.ts +175 -0
- package/dist/{_init-CbkrE2wq.mjs → _init-CpISqoR8.mjs} +3 -3
- package/dist/{_output-CC300DzW.mjs → _output-CKkmWs7i.mjs} +8 -1
- package/dist/_output.d.ts +9 -2
- package/dist/{_server-common-61fL__39.mjs → _server-common-6e0QI4mq.mjs} +1 -1
- package/dist/{_slug-api-D30zPmsn.mjs → _slug-api-D2heILuC.mjs} +2 -2
- package/dist/{_studio-D2ciIhbi.mjs → _studio-zAJycZ8j.mjs} +3 -3
- package/dist/{_templates-B1kt3BGt.mjs → _templates-iDGjGiPf.mjs} +4 -6
- package/dist/{_typecheck-gate-4v9UBZHh.mjs → _typecheck-gate-BXvUNnfs.mjs} +1 -1
- package/dist/{_utils-0DHbcqUb.mjs → _utils-B8QmtFhK.mjs} +21 -7
- package/dist/_utils.d.ts +18 -2
- package/dist/{_vite-env-Dg_QlVv0.mjs → _vite-env-BNveawd1.mjs} +31 -1
- package/dist/_vite-env.d.ts +30 -0
- package/dist/build-BO1Ni6oJ.mjs +62 -0
- package/dist/build.d.ts +19 -0
- package/dist/cli-workflow.d.ts +10 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.mjs +406 -224
- package/dist/{client-bundler-C9qXL8A2.mjs → client-bundler-B8Q9F8gJ.mjs} +3 -22
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-DCRX3d96.mjs → delete-4I0uNR4a.mjs} +6 -7
- package/dist/delete.d.ts +2 -8
- package/dist/{deploy-DjsEV-40.mjs → deploy-DJ3HRcia.mjs} +13 -14
- package/dist/{dev-CvPH5dbF.mjs → dev-C6SWaAZP.mjs} +3 -3
- package/dist/{eject-LgBRfnbH.mjs → eject-Zrzk5KV3.mjs} +41 -15
- package/dist/eject.d.ts +30 -3
- package/dist/{init-BppxMR8r.mjs → init-BTRGiPQr.mjs} +61 -26
- package/dist/init.d.ts +11 -0
- package/dist/{login-BQQaxJhL.mjs → login-k0Z3VQu9.mjs} +5 -5
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +498 -91
- package/dist/scaffold/package.json +11 -8
- package/dist/scaffold/pnpm-workspace.yaml +8 -0
- package/dist/scaffold/server.mjs +53 -57
- package/dist/{secret-Ds9dvqAj.mjs → secret-DzSQx1Ds.mjs} +6 -3
- package/dist/{storage-DbrQSHFk.mjs → storage-C-Do0mIA.mjs} +18 -6
- package/dist/{studio-GSFd4s5O.mjs → studio-BGVmCA2G.mjs} +10 -10
- package/dist/templates/code-interpreter/agent.ts +0 -2
- package/dist/templates/dispatch-center/agent.test.ts +17 -19
- package/dist/templates/dispatch-center/agent.ts +2 -29
- package/dist/templates/dispatch-center/client.tsx +33 -16
- package/dist/templates/dispatch-center/shared.ts +53 -19
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +12 -14
- package/dist/templates/dispatch-center/tools/incident_create.ts +41 -44
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +59 -61
- package/dist/templates/dispatch-center/tools/incident_get.ts +3 -4
- package/dist/templates/dispatch-center/tools/incident_triage.ts +43 -45
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +39 -41
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +2 -5
- package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +20 -23
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +52 -54
- package/dist/templates/dispatch-center/tools/resources_get_available.ts +2 -4
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +39 -42
- package/dist/templates/embedded-assets/agent.test.ts +83 -0
- package/dist/templates/embedded-assets/agent.ts +1 -48
- package/dist/templates/embedded-assets/shared.ts +17 -0
- package/dist/templates/embedded-assets/system-prompt.md +13 -0
- package/dist/templates/embedded-assets/tools/list_topics.ts +9 -0
- package/dist/templates/embedded-assets/tools/search_knowledge.ts +26 -0
- package/dist/templates/health-assistant/agent.test.ts +169 -0
- package/dist/templates/health-assistant/agent.ts +1 -166
- package/dist/templates/health-assistant/fda.ts +100 -0
- package/dist/templates/health-assistant/tools/check_drug_interaction.ts +59 -0
- package/dist/templates/health-assistant/tools/medication_lookup.ts +31 -0
- package/dist/templates/infocom-adventure/agent.test.ts +205 -0
- package/dist/templates/infocom-adventure/agent.ts +3 -106
- package/dist/templates/infocom-adventure/client.tsx +28 -12
- package/dist/templates/infocom-adventure/shared.ts +28 -11
- package/dist/templates/infocom-adventure/tools/game_state_drop.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_flag.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_get.ts +16 -0
- package/dist/templates/infocom-adventure/tools/game_state_history.ts +14 -0
- package/dist/templates/infocom-adventure/tools/game_state_move.ts +14 -0
- package/dist/templates/infocom-adventure/tools/game_state_restart.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_score.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_take.ts +13 -0
- package/dist/templates/link-digest/agent.test.ts +223 -0
- package/dist/templates/link-digest/agent.ts +79 -0
- package/dist/templates/link-digest/client.tsx +146 -0
- package/dist/templates/link-digest/workflows/digest.ts +248 -0
- package/dist/templates/math-buddy/agent.ts +0 -2
- package/dist/templates/night-owl/agent.test.ts +80 -0
- package/dist/templates/night-owl/agent.ts +1 -68
- package/dist/templates/night-owl/system-prompt.md +10 -0
- package/dist/templates/night-owl/tools/recommend.ts +64 -0
- package/dist/templates/personal-finance/agent.ts +0 -2
- package/dist/templates/pizza-ordering/agent.test.ts +52 -47
- package/dist/templates/pizza-ordering/agent.ts +9 -158
- package/dist/templates/pizza-ordering/shared.ts +35 -12
- package/dist/templates/pizza-ordering/tools/add_pizza.ts +31 -0
- package/dist/templates/pizza-ordering/tools/place_order.ts +22 -0
- package/dist/templates/pizza-ordering/tools/remove_pizza.ts +22 -0
- package/dist/templates/pizza-ordering/tools/set_customer_name.ts +13 -0
- package/dist/templates/pizza-ordering/tools/update_pizza.ts +28 -0
- package/dist/templates/pizza-ordering/tools/view_order.ts +21 -0
- package/dist/templates/plan-and-execute/agent.test.ts +411 -0
- package/dist/templates/plan-and-execute/agent.ts +23 -0
- package/dist/templates/plan-and-execute/client.tsx +115 -0
- package/dist/templates/plan-and-execute/graph.ts +178 -0
- package/dist/templates/plan-and-execute/prompts.ts +110 -0
- package/dist/templates/plan-and-execute/shared.ts +161 -0
- package/dist/templates/plan-and-execute/system-prompt.md +18 -0
- package/dist/templates/plan-and-execute/tools/plan_status.ts +20 -0
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +63 -0
- package/dist/templates/plan-and-execute/tools/start_plan.ts +51 -0
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +101 -0
- package/dist/templates/recap-workflow/agent.test.ts +762 -0
- package/dist/templates/recap-workflow/agent.ts +95 -0
- package/dist/templates/recap-workflow/shared.ts +43 -0
- package/dist/templates/recap-workflow/tools/cancel_recap.ts +23 -0
- package/dist/templates/recap-workflow/tools/keep_transcript.ts +31 -0
- package/dist/templates/recap-workflow/tools/recap_progress.ts +27 -0
- package/dist/templates/recap-workflow/tools/recap_status.ts +46 -0
- package/dist/templates/recap-workflow/tools/request_recap.ts +43 -0
- package/dist/templates/recap-workflow/workflows/recap.ts +544 -0
- package/dist/templates/recap-workflow/workflows/tokens.ts +26 -0
- package/dist/templates/redline/agent.test.ts +222 -0
- package/dist/templates/redline/agent.ts +89 -0
- package/dist/templates/redline/client.tsx +184 -0
- package/dist/templates/redline/workflows/prompts.ts +68 -0
- package/dist/templates/redline/workflows/redline.ts +258 -0
- package/dist/templates/research-workflow/agent.test.ts +525 -0
- package/dist/templates/research-workflow/agent.ts +94 -0
- package/dist/templates/research-workflow/shared.ts +35 -0
- package/dist/templates/research-workflow/tools/file_it_now.ts +22 -0
- package/dist/templates/research-workflow/tools/request_research.ts +33 -0
- package/dist/templates/research-workflow/tools/research_progress.ts +26 -0
- package/dist/templates/research-workflow/tools/research_status.ts +40 -0
- package/dist/templates/research-workflow/workflows/prompts.ts +143 -0
- package/dist/templates/research-workflow/workflows/research.ts +558 -0
- package/dist/templates/retail/agent.test.ts +236 -172
- package/dist/templates/retail/agent.ts +5 -42
- package/dist/templates/retail/client.tsx +33 -11
- package/dist/templates/retail/registry.test.ts +31 -16
- package/dist/templates/retail/resolve.ts +28 -64
- package/dist/templates/retail/shared.test.ts +4 -3
- package/dist/templates/retail/shared.ts +24 -7
- package/dist/templates/retail/store.test.ts +12 -10
- package/dist/templates/retail/store.ts +69 -30
- package/dist/templates/retail/tools/cancel_pending_order.ts +4 -9
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -11
- package/dist/templates/retail/tools/find_user_id_by_email.ts +5 -9
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +5 -7
- package/dist/templates/retail/tools/get_item_details.ts +5 -7
- package/dist/templates/retail/tools/get_order_details.ts +5 -7
- package/dist/templates/retail/tools/get_product_details.ts +4 -7
- package/dist/templates/retail/tools/get_user_details.ts +4 -7
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -7
- package/dist/templates/retail/tools/modify_pending_order_address.ts +4 -9
- package/dist/templates/retail/tools/modify_pending_order_items.ts +4 -16
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +3 -9
- package/dist/templates/retail/tools/modify_user_address.ts +4 -9
- package/dist/templates/retail/tools/return_delivered_order_items.ts +3 -9
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +1 -1
- package/dist/templates/solo-rpg/agent.test.ts +152 -29
- package/dist/templates/solo-rpg/agent.ts +3 -21
- package/dist/templates/solo-rpg/client.tsx +19 -6
- package/dist/templates/solo-rpg/shared.ts +19 -11
- package/dist/templates/solo-rpg/tools/action_roll.ts +2 -4
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +2 -4
- package/dist/templates/solo-rpg/tools/check_state.ts +6 -6
- package/dist/templates/solo-rpg/tools/load_game.ts +1 -1
- package/dist/templates/solo-rpg/tools/oracle.ts +18 -11
- package/dist/templates/solo-rpg/tools/save_game.ts +10 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +1 -1
- package/dist/templates/solo-rpg/tools/update_state.ts +3 -4
- package/dist/templates/support-line/agent.test.ts +343 -0
- package/dist/templates/support-line/agent.ts +27 -0
- package/dist/templates/support-line/client.tsx +122 -0
- package/dist/templates/support-line/graph.ts +224 -0
- package/dist/templates/support-line/knowledge.json +65 -0
- package/dist/templates/support-line/prompts.ts +95 -0
- package/dist/templates/support-line/shared.ts +240 -0
- package/dist/templates/support-line/system-prompt.md +17 -0
- package/dist/templates/support-line/tools/answer_question.ts +72 -0
- package/dist/templates/support-line/tools/list_topics.ts +22 -0
- package/dist/templates/support-line/tools/log_ticket.ts +30 -0
- package/dist/templates/transcription-workflow/agent.test.ts +563 -0
- package/dist/templates/transcription-workflow/agent.ts +92 -0
- package/dist/templates/transcription-workflow/client.tsx +234 -0
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +416 -0
- package/dist/templates/transcription-workflow/workflows/wav.ts +314 -0
- package/dist/templates/travel-concierge/agent.test.ts +338 -0
- package/dist/templates/travel-concierge/agent.ts +29 -0
- package/dist/templates/travel-concierge/client.tsx +161 -0
- package/dist/templates/travel-concierge/routing.ts +138 -0
- package/dist/templates/travel-concierge/shared.ts +547 -0
- package/dist/templates/travel-concierge/system-prompt.md +24 -0
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +20 -0
- package/dist/templates/travel-concierge/tools/book_excursion.ts +18 -0
- package/dist/templates/travel-concierge/tools/book_hotel.ts +20 -0
- package/dist/templates/travel-concierge/tools/cancel_action.ts +7 -0
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +16 -0
- package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +7 -0
- package/dist/templates/travel-concierge/tools/confirm_action.ts +7 -0
- package/dist/templates/travel-concierge/tools/lookup_booking.ts +35 -0
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +33 -0
- package/dist/templates/travel-concierge/tools/search_excursions.ts +37 -0
- package/dist/templates/travel-concierge/tools/search_flights.ts +43 -0
- package/dist/templates/travel-concierge/tools/search_hotels.ts +36 -0
- package/dist/templates/travel-concierge/tools/to_car_rental_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_excursion_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_flight_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_hotel_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/update_ticket.ts +22 -0
- package/dist/templates/web-researcher/agent.ts +0 -2
- package/dist/templates/web-researcher/system-prompt.md +10 -0
- package/dist/{test-2LWBcXvY.mjs → test-B6ZBxSpk.mjs} +2 -2
- package/dist/typecheck.mjs +17 -15
- package/dist/worker-bundler-CIdDhZ0c.mjs +398 -0
- package/dist/worker-bundler.d.ts +14 -0
- package/dist/worker-bundler.mjs +1 -108
- package/dist/workflow-B-O97AqI.mjs +125 -0
- package/dist/workflow-bundler.d.ts +133 -0
- package/dist/workflow.d.ts +67 -0
- package/package.json +13 -9
- package/dist/build-DQJ1ZSm9.mjs +0 -39
|
@@ -1,53 +1,67 @@
|
|
|
1
|
-
import type { ToolContext
|
|
1
|
+
import type { ToolContext } from "@alexkroman1/aai";
|
|
2
2
|
import { isToolFailure } from "@alexkroman1/aai";
|
|
3
3
|
import { createToolContext } from "@alexkroman1/aai/testing";
|
|
4
4
|
import { describe, expect, test } from "vitest";
|
|
5
5
|
import type { AuthResult } from "./authenticate.ts";
|
|
6
6
|
import type { Address } from "./shared.ts";
|
|
7
|
-
import { retailSlot
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import
|
|
7
|
+
import { retailSlot } from "./store.ts";
|
|
8
|
+
import cancelPendingOrder from "./tools/cancel_pending_order.ts";
|
|
9
|
+
import exchangeDeliveredOrderItems from "./tools/exchange_delivered_order_items.ts";
|
|
10
|
+
import findUserIdByEmail from "./tools/find_user_id_by_email.ts";
|
|
11
|
+
import findUserIdByNameZip from "./tools/find_user_id_by_name_zip.ts";
|
|
12
|
+
import getItemDetails from "./tools/get_item_details.ts";
|
|
13
|
+
import getOrderDetails from "./tools/get_order_details.ts";
|
|
14
|
+
import getProductDetails from "./tools/get_product_details.ts";
|
|
15
|
+
import getUserDetails from "./tools/get_user_details.ts";
|
|
16
|
+
import listAllProductTypes from "./tools/list_all_product_types.ts";
|
|
17
|
+
import modifyPendingOrderAddress from "./tools/modify_pending_order_address.ts";
|
|
18
|
+
import modifyPendingOrderItems from "./tools/modify_pending_order_items.ts";
|
|
19
|
+
import modifyPendingOrderPayment from "./tools/modify_pending_order_payment.ts";
|
|
20
|
+
import modifyUserAddress from "./tools/modify_user_address.ts";
|
|
21
|
+
import returnDeliveredOrderItems from "./tools/return_delivered_order_items.ts";
|
|
22
|
+
import transferToHumanAgents from "./tools/transfer_to_human_agents.ts";
|
|
23
23
|
|
|
24
24
|
/** Each call is its own session, so two contexts are two independent stores —
|
|
25
25
|
* which is what the isolation tests below rest on. */
|
|
26
|
-
function makeCtx(): ToolContext
|
|
27
|
-
return createToolContext
|
|
26
|
+
function makeCtx(): ToolContext {
|
|
27
|
+
return createToolContext();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The success half of a tool result, or a thrown failure carrying the tool's
|
|
32
|
+
* own message.
|
|
33
|
+
*
|
|
34
|
+
* `ToolDef["execute"]`'s public signature always returns `unknown` — the wire
|
|
35
|
+
* type is fixed so any tool is assignable to `ToolDef`, whatever its body
|
|
36
|
+
* really returns — so every test that reads a field back off a result needed a
|
|
37
|
+
* cast. There were EIGHTEEN of them here, each the same three lines, which is
|
|
38
|
+
* the shape the repo's guides call a missing typed seam: a concentration of
|
|
39
|
+
* identical casts is one narrowing that belongs in one helper, not a cast per
|
|
40
|
+
* assertion. This is that helper, and it is the file's only cast.
|
|
41
|
+
*
|
|
42
|
+
* It also removes the second half of the boilerplate: a `ToolFailure` fails the
|
|
43
|
+
* test HERE, naming what the tool refused, rather than surfacing three lines
|
|
44
|
+
* later as `undefined` on a field nobody assigned.
|
|
45
|
+
*/
|
|
46
|
+
function ok<T>(result: unknown): T {
|
|
47
|
+
if (isToolFailure(result)) throw new Error(`tool refused: ${result.error}`);
|
|
48
|
+
return result as T;
|
|
28
49
|
}
|
|
29
50
|
|
|
30
51
|
/** A context already authenticated as `userId`, via the real tool. */
|
|
31
|
-
async function authedCtx(email: string): Promise<ToolContext
|
|
52
|
+
async function authedCtx(email: string): Promise<ToolContext> {
|
|
32
53
|
const ctx = makeCtx();
|
|
33
|
-
|
|
34
|
-
// wire type is fixed so any tool is assignable to `ToolDef`, regardless of
|
|
35
|
-
// what its own `execute` body actually returns), so a cast is needed
|
|
36
|
-
// anywhere a test reads a success field back off the result — matching the
|
|
37
|
-
// `as {...}` convention other templates already use for the same reason.
|
|
38
|
-
const result = (await findUserIdByEmail.execute({ email }, ctx)) as AuthResult | ToolFailure;
|
|
39
|
-
if (isToolFailure(result)) throw new Error(`fixture failed to authenticate: ${result.error}`);
|
|
54
|
+
ok<AuthResult>(await findUserIdByEmail.execute({ email }, ctx));
|
|
40
55
|
return ctx;
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
describe("authentication", () => {
|
|
44
59
|
test("find_user_id_by_email is case-insensitive and authenticates the session", async () => {
|
|
45
60
|
const ctx = makeCtx();
|
|
46
|
-
const result = (
|
|
47
|
-
{ email: "OLIVIA.ITO5204@EXAMPLE.COM" },
|
|
48
|
-
|
|
49
|
-
))
|
|
50
|
-
expect(isToolFailure(result) ? null : result.user_id).toBe("olivia_ito_3591");
|
|
61
|
+
const result = ok<AuthResult>(
|
|
62
|
+
await findUserIdByEmail.execute({ email: "OLIVIA.ITO5204@EXAMPLE.COM" }, ctx),
|
|
63
|
+
);
|
|
64
|
+
expect(result.user_id).toBe("olivia_ito_3591");
|
|
51
65
|
expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
|
|
52
66
|
});
|
|
53
67
|
|
|
@@ -60,11 +74,13 @@ describe("authentication", () => {
|
|
|
60
74
|
|
|
61
75
|
test("find_user_id_by_name_zip is case-insensitive on names and exact on zip", async () => {
|
|
62
76
|
const ctx = makeCtx();
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
const found = ok<AuthResult>(
|
|
78
|
+
await findUserIdByNameZip.execute(
|
|
79
|
+
{ first_name: "aarav", last_name: "ANDERSON", zip: "19031" },
|
|
80
|
+
ctx,
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
expect(found.user_id).toBe("aarav_anderson_8794");
|
|
68
84
|
|
|
69
85
|
const wrongZip = await findUserIdByNameZip.execute(
|
|
70
86
|
{ first_name: "Aarav", last_name: "Anderson", zip: "78268" },
|
|
@@ -74,16 +90,20 @@ describe("authentication", () => {
|
|
|
74
90
|
});
|
|
75
91
|
|
|
76
92
|
test("two customers share a first name — the zip is what separates them", async () => {
|
|
77
|
-
const a = (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
const a = ok<AuthResult>(
|
|
94
|
+
await findUserIdByNameZip.execute(
|
|
95
|
+
{ first_name: "Aarav", last_name: "Anderson", zip: "19031" },
|
|
96
|
+
makeCtx(),
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
const b = ok<AuthResult>(
|
|
100
|
+
await findUserIdByNameZip.execute(
|
|
101
|
+
{ first_name: "Aarav", last_name: "Gonzalez", zip: "78268" },
|
|
102
|
+
makeCtx(),
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
expect(a.user_id).toBe("aarav_anderson_8794");
|
|
106
|
+
expect(b.user_id).toBe("aarav_gonzalez_5113");
|
|
87
107
|
});
|
|
88
108
|
|
|
89
109
|
test("re-authenticating as the same customer is a no-op success", async () => {
|
|
@@ -142,10 +162,9 @@ interface ProductTypesResult {
|
|
|
142
162
|
describe("read tools", () => {
|
|
143
163
|
test("get_user_details returns the caller's profile with gift-card balances", async () => {
|
|
144
164
|
const ctx = await authedCtx("olivia.ito5204@example.com");
|
|
145
|
-
const result = (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (isToolFailure(result)) throw new Error(result.error);
|
|
165
|
+
const result = ok<UserDetailsResult>(
|
|
166
|
+
await getUserDetails.execute({ user_id: "olivia_ito_3591" }, ctx),
|
|
167
|
+
);
|
|
149
168
|
expect(result.email).toBe("olivia.ito5204@example.com");
|
|
150
169
|
expect(result.orders).toHaveLength(5);
|
|
151
170
|
const card = result.payment_methods.find((m) => m.payment_method_id === "gift_card_7794233");
|
|
@@ -160,10 +179,9 @@ describe("read tools", () => {
|
|
|
160
179
|
|
|
161
180
|
test("get_order_details resolves shorthand and sets focus", async () => {
|
|
162
181
|
const ctx = await authedCtx("olivia.ito5204@example.com");
|
|
163
|
-
const result = (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (isToolFailure(result)) throw new Error(result.error);
|
|
182
|
+
const result = ok<OrderDetailsResult>(
|
|
183
|
+
await getOrderDetails.execute({ order_id: "the delivered one" }, ctx),
|
|
184
|
+
);
|
|
167
185
|
expect(result.order_id).toBe("#W5866402");
|
|
168
186
|
expect(retailSlot.get(ctx).focus.orderId).toBe("#W5866402");
|
|
169
187
|
});
|
|
@@ -175,10 +193,9 @@ describe("read tools", () => {
|
|
|
175
193
|
});
|
|
176
194
|
|
|
177
195
|
test("get_product_details lists variants and needs no authentication", async () => {
|
|
178
|
-
const result = (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (isToolFailure(result)) throw new Error(result.error);
|
|
196
|
+
const result = ok<ProductDetailsResult>(
|
|
197
|
+
await getProductDetails.execute({ product_id: "9832717871" }, makeCtx()),
|
|
198
|
+
);
|
|
182
199
|
expect(result.name).toBe("Tea Kettle");
|
|
183
200
|
expect(result.variants.length).toBeGreaterThan(1);
|
|
184
201
|
expect(result.variants[0]).toHaveProperty("item_id");
|
|
@@ -190,19 +207,15 @@ describe("read tools", () => {
|
|
|
190
207
|
});
|
|
191
208
|
|
|
192
209
|
test("get_item_details resolves an item without knowing its product", async () => {
|
|
193
|
-
const result = (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (isToolFailure(result)) throw new Error(result.error);
|
|
210
|
+
const result = ok<ItemDetailsResult>(
|
|
211
|
+
await getItemDetails.execute({ item_id: "3909406921" }, makeCtx()),
|
|
212
|
+
);
|
|
197
213
|
expect(result.price).toBe(98.25);
|
|
198
214
|
expect(result.product_name).toBe("Tea Kettle");
|
|
199
215
|
});
|
|
200
216
|
|
|
201
217
|
test("list_all_product_types returns all 50, sorted by name", async () => {
|
|
202
|
-
const result = (await listAllProductTypes.execute({}, makeCtx()))
|
|
203
|
-
| ProductTypesResult
|
|
204
|
-
| ToolFailure;
|
|
205
|
-
if (isToolFailure(result)) throw new Error(result.error);
|
|
218
|
+
const result = ok<ProductTypesResult>(await listAllProductTypes.execute({}, makeCtx()));
|
|
206
219
|
expect(Object.keys(result.products)).toHaveLength(50);
|
|
207
220
|
const names = Object.keys(result.products);
|
|
208
221
|
// localeCompare, not the default lexicographic sort — the tool sorts for a
|
|
@@ -224,11 +237,12 @@ interface CancelOrderResult {
|
|
|
224
237
|
describe("cancel_pending_order", () => {
|
|
225
238
|
test("cancels a pending order and credits a gift card immediately", async () => {
|
|
226
239
|
const ctx = await authedCtx("aarav.anderson9752@example.com");
|
|
227
|
-
const result = (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
240
|
+
const result = ok<CancelOrderResult>(
|
|
241
|
+
await cancelPendingOrder.execute(
|
|
242
|
+
{ order_id: "#W9300146", reason: "ordered by mistake" },
|
|
243
|
+
ctx,
|
|
244
|
+
),
|
|
245
|
+
);
|
|
232
246
|
expect(result.status).toBe("cancelled");
|
|
233
247
|
expect(result.refund_immediate).toBe(true);
|
|
234
248
|
|
|
@@ -254,11 +268,9 @@ describe("cancel_pending_order", () => {
|
|
|
254
268
|
|
|
255
269
|
test("a non-gift-card refund takes 5-7 days and moves no balance", async () => {
|
|
256
270
|
const ctx = await authedCtx("olivia.ito5204@example.com");
|
|
257
|
-
const result = (
|
|
258
|
-
{ order_id: "#W5442520", reason: "no longer needed" },
|
|
259
|
-
|
|
260
|
-
)) as CancelOrderResult | ToolFailure;
|
|
261
|
-
if (isToolFailure(result)) throw new Error(result.error);
|
|
271
|
+
const result = ok<CancelOrderResult>(
|
|
272
|
+
await cancelPendingOrder.execute({ order_id: "#W5442520", reason: "no longer needed" }, ctx),
|
|
273
|
+
);
|
|
262
274
|
expect(result.refund_immediate).toBe(false);
|
|
263
275
|
expect(result.message).toContain("5 to 7");
|
|
264
276
|
const card = retailSlot.get(ctx).store.users.olivia_ito_3591?.payment_methods.gift_card_7794233;
|
|
@@ -312,11 +324,13 @@ describe("cancel_pending_order", () => {
|
|
|
312
324
|
|
|
313
325
|
test("resolves spoken shorthand to the single pending order", async () => {
|
|
314
326
|
const ctx = await authedCtx("aarav.anderson9752@example.com");
|
|
315
|
-
const result = (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
327
|
+
const result = ok<CancelOrderResult>(
|
|
328
|
+
await cancelPendingOrder.execute(
|
|
329
|
+
{ order_id: "my pending order", reason: "ordered by mistake" },
|
|
330
|
+
ctx,
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
expect(result.order_id).toBe("#W9300146");
|
|
320
334
|
});
|
|
321
335
|
|
|
322
336
|
test("refuses ambiguous shorthand rather than cancelling the wrong order", async () => {
|
|
@@ -350,11 +364,9 @@ interface AddressToolResult {
|
|
|
350
364
|
describe("modify_pending_order_address", () => {
|
|
351
365
|
test("rewrites a pending order's shipping address", async () => {
|
|
352
366
|
const ctx = await authedCtx("aarav.anderson9752@example.com");
|
|
353
|
-
const result = (
|
|
354
|
-
{ order_id: "#W9300146", ...NEW_ADDRESS },
|
|
355
|
-
|
|
356
|
-
)) as AddressToolResult | ToolFailure;
|
|
357
|
-
if (isToolFailure(result)) throw new Error(result.error);
|
|
367
|
+
const result = ok<AddressToolResult>(
|
|
368
|
+
await modifyPendingOrderAddress.execute({ order_id: "#W9300146", ...NEW_ADDRESS }, ctx),
|
|
369
|
+
);
|
|
358
370
|
expect(result.address).toEqual(NEW_ADDRESS);
|
|
359
371
|
expect(retailSlot.get(ctx).store.orders["#W9300146"]?.address).toEqual(NEW_ADDRESS);
|
|
360
372
|
});
|
|
@@ -367,9 +379,11 @@ describe("modify_pending_order_address", () => {
|
|
|
367
379
|
|
|
368
380
|
test("accepts a 'pending (item modified)' order — unlike cancel", async () => {
|
|
369
381
|
const ctx = await authedCtx("aarav.anderson9752@example.com");
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
382
|
+
retailSlot.update(ctx, (state) => {
|
|
383
|
+
const order = state.store.orders["#W9300146"];
|
|
384
|
+
if (!order) throw new Error("fixture missing");
|
|
385
|
+
order.status = "pending (item modified)";
|
|
386
|
+
});
|
|
373
387
|
const result = await modifyPendingOrderAddress.execute(
|
|
374
388
|
{ order_id: "#W9300146", ...NEW_ADDRESS },
|
|
375
389
|
ctx,
|
|
@@ -399,11 +413,7 @@ describe("modify_pending_order_address", () => {
|
|
|
399
413
|
describe("modify_user_address", () => {
|
|
400
414
|
test("rewrites the customer's default address", async () => {
|
|
401
415
|
const ctx = await authedCtx("emma.smith3991@example.com");
|
|
402
|
-
|
|
403
|
-
{ user_id: "emma_smith_8564", ...NEW_ADDRESS },
|
|
404
|
-
ctx,
|
|
405
|
-
);
|
|
406
|
-
if (isToolFailure(result)) throw new Error(result.error);
|
|
416
|
+
ok(await modifyUserAddress.execute({ user_id: "emma_smith_8564", ...NEW_ADDRESS }, ctx));
|
|
407
417
|
expect(retailSlot.get(ctx).store.users.emma_smith_8564?.address).toEqual(NEW_ADDRESS);
|
|
408
418
|
});
|
|
409
419
|
|
|
@@ -444,16 +454,17 @@ interface ModifyItemsResult {
|
|
|
444
454
|
describe("modify_pending_order_items", () => {
|
|
445
455
|
test("swaps an item, charges the difference to a gift card, and goes terminal", async () => {
|
|
446
456
|
const ctx = await authedCtx("emma.smith3991@example.com");
|
|
447
|
-
const result = (
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
+
const result = ok<ModifyItemsResult>(
|
|
458
|
+
await modifyPendingOrderItems.execute(
|
|
459
|
+
{
|
|
460
|
+
order_id: "#W2417020",
|
|
461
|
+
item_ids: ["8997785118"],
|
|
462
|
+
new_item_ids: ["6017636844"],
|
|
463
|
+
payment_method_id: "gift_card_8541487",
|
|
464
|
+
},
|
|
465
|
+
ctx,
|
|
466
|
+
),
|
|
467
|
+
);
|
|
457
468
|
expect(result.price_difference).toBe(-382.03);
|
|
458
469
|
expect(result.status).toBe("pending (item modified)");
|
|
459
470
|
|
|
@@ -554,16 +565,17 @@ describe("modify_pending_order_items", () => {
|
|
|
554
565
|
|
|
555
566
|
test("the same upgrade succeeds on a payment method with no balance to run out of", async () => {
|
|
556
567
|
const ctx = await authedCtx("anya.garcia2061@example.com");
|
|
557
|
-
const result = (
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
568
|
+
const result = ok<ModifyItemsResult>(
|
|
569
|
+
await modifyPendingOrderItems.execute(
|
|
570
|
+
{
|
|
571
|
+
order_id: "#W6436609",
|
|
572
|
+
item_ids: ["6017636844"],
|
|
573
|
+
new_item_ids: ["9844888101"],
|
|
574
|
+
payment_method_id: "credit_card_8955149",
|
|
575
|
+
},
|
|
576
|
+
ctx,
|
|
577
|
+
),
|
|
578
|
+
);
|
|
567
579
|
expect(result.price_difference).toBe(167.37);
|
|
568
580
|
const history = retailSlot.get(ctx).store.orders["#W6436609"]?.payment_history ?? [];
|
|
569
581
|
expect(history[1]).toMatchObject({
|
|
@@ -616,11 +628,12 @@ describe("modify_pending_order_payment", () => {
|
|
|
616
628
|
test("moves an order to a different method, refunding the old one", async () => {
|
|
617
629
|
const ctx = await authedCtx("olivia.ito5204@example.com");
|
|
618
630
|
// #W5442520 is $663.85 on credit_card_9753331. Pay by PayPal instead.
|
|
619
|
-
const result = (
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
631
|
+
const result = ok<ModifyPaymentResult>(
|
|
632
|
+
await modifyPendingOrderPayment.execute(
|
|
633
|
+
{ order_id: "#W5442520", payment_method_id: "paypal_8049766" },
|
|
634
|
+
ctx,
|
|
635
|
+
),
|
|
636
|
+
);
|
|
624
637
|
expect(result.status).toBe("pending");
|
|
625
638
|
|
|
626
639
|
const history = retailSlot.get(ctx).store.orders["#W5442520"]?.payment_history ?? [];
|
|
@@ -675,14 +688,16 @@ describe("modify_pending_order_payment", () => {
|
|
|
675
688
|
|
|
676
689
|
test("refuses an order whose payment history is not a single payment", async () => {
|
|
677
690
|
const ctx = await authedCtx("olivia.ito5204@example.com");
|
|
678
|
-
const order = retailSlot.get(ctx).store.orders["#W5442520"];
|
|
679
|
-
if (!order) throw new Error("fixture missing");
|
|
680
691
|
// No seeded pending order has a second history entry, so construct one —
|
|
681
692
|
// tau2 guards this case and the guard should still be covered.
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
693
|
+
retailSlot.update(ctx, (state) => {
|
|
694
|
+
const order = state.store.orders["#W5442520"];
|
|
695
|
+
if (!order) throw new Error("fixture missing");
|
|
696
|
+
order.payment_history.push({
|
|
697
|
+
transaction_type: "refund",
|
|
698
|
+
amount: 10,
|
|
699
|
+
payment_method_id: "credit_card_9753331",
|
|
700
|
+
});
|
|
686
701
|
});
|
|
687
702
|
const result = await modifyPendingOrderPayment.execute(
|
|
688
703
|
{ order_id: "#W5442520", payment_method_id: "paypal_8049766" },
|
|
@@ -722,23 +737,23 @@ interface ExchangeResult {
|
|
|
722
737
|
order_id: string;
|
|
723
738
|
status: string;
|
|
724
739
|
price_difference: number;
|
|
725
|
-
|
|
726
|
-
exchange_new_items: string[];
|
|
740
|
+
exchanges: { item_id: string; new_item_id: string; price_difference: number }[];
|
|
727
741
|
message: string;
|
|
728
742
|
}
|
|
729
743
|
|
|
730
744
|
describe("return_delivered_order_items", () => {
|
|
731
745
|
test("requests a return refunded to the original method", async () => {
|
|
732
746
|
const ctx = await authedCtx("harper.brown3965@example.com");
|
|
733
|
-
const result = (
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
747
|
+
const result = ok<ReturnResult>(
|
|
748
|
+
await returnDeliveredOrderItems.execute(
|
|
749
|
+
{
|
|
750
|
+
order_id: "#W1840144",
|
|
751
|
+
item_ids: ["8590708195"],
|
|
752
|
+
payment_method_id: "paypal_2306935",
|
|
753
|
+
},
|
|
754
|
+
ctx,
|
|
755
|
+
),
|
|
756
|
+
);
|
|
742
757
|
expect(result.status).toBe("return requested");
|
|
743
758
|
const order = retailSlot.get(ctx).store.orders["#W1840144"];
|
|
744
759
|
expect(order?.return_items).toEqual(["8590708195"]);
|
|
@@ -832,17 +847,21 @@ describe("return_delivered_order_items", () => {
|
|
|
832
847
|
describe("exchange_delivered_order_items", () => {
|
|
833
848
|
test("records an exchange with a positive price difference", async () => {
|
|
834
849
|
const ctx = await authedCtx("aarav.anderson9752@example.com");
|
|
835
|
-
const result = (
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
850
|
+
const result = ok<ExchangeResult>(
|
|
851
|
+
await exchangeDeliveredOrderItems.execute(
|
|
852
|
+
{
|
|
853
|
+
order_id: "#W4316152",
|
|
854
|
+
item_ids: ["7292993796"],
|
|
855
|
+
new_item_ids: ["3909406921"],
|
|
856
|
+
payment_method_id: "gift_card_7245904",
|
|
857
|
+
},
|
|
858
|
+
ctx,
|
|
859
|
+
),
|
|
860
|
+
);
|
|
845
861
|
expect(result.price_difference).toBe(3.45);
|
|
862
|
+
expect(result.exchanges).toEqual([
|
|
863
|
+
{ item_id: "7292993796", new_item_id: "3909406921", price_difference: 3.45 },
|
|
864
|
+
]);
|
|
846
865
|
const order = retailSlot.get(ctx).store.orders["#W4316152"];
|
|
847
866
|
expect(order?.status).toBe("exchange requested");
|
|
848
867
|
expect(order?.exchange_items).toEqual(["7292993796"]);
|
|
@@ -850,6 +869,45 @@ describe("exchange_delivered_order_items", () => {
|
|
|
850
869
|
expect(order?.exchange_price_difference).toBe(3.45);
|
|
851
870
|
});
|
|
852
871
|
|
|
872
|
+
test("the ANSWER pairs by position even where the stored fields sort separately", async () => {
|
|
873
|
+
// `#W5866402` is the case that makes the difference visible: the two
|
|
874
|
+
// requested replacements sort into the OTHER order, so the two stored lists
|
|
875
|
+
// — tau2's `sorted(item_ids)` / `sorted(new_item_ids)`, kept for fidelity
|
|
876
|
+
// with its expected end states — read as espresso→sneaker if anything
|
|
877
|
+
// treats them as a pairing. `planItemSwap` priced this positionally, so the
|
|
878
|
+
// result has to report the pairing that was priced, not the one the sort
|
|
879
|
+
// produced.
|
|
880
|
+
const ctx = await authedCtx("olivia.ito5204@example.com");
|
|
881
|
+
const result = ok<ExchangeResult>(
|
|
882
|
+
await exchangeDeliveredOrderItems.execute(
|
|
883
|
+
{
|
|
884
|
+
order_id: "#W5866402",
|
|
885
|
+
item_ids: ["6242772310", "9727387530"],
|
|
886
|
+
new_item_ids: ["7407838442", "2509076505"],
|
|
887
|
+
payment_method_id: "paypal_8049766",
|
|
888
|
+
},
|
|
889
|
+
ctx,
|
|
890
|
+
),
|
|
891
|
+
);
|
|
892
|
+
|
|
893
|
+
expect(result.exchanges).toEqual([
|
|
894
|
+
{ item_id: "6242772310", new_item_id: "7407838442", price_difference: 85.88 },
|
|
895
|
+
{ item_id: "9727387530", new_item_id: "2509076505", price_difference: -18.25 },
|
|
896
|
+
]);
|
|
897
|
+
// The per-line differences add up to the quoted total — which is what makes
|
|
898
|
+
// the answer internally consistent rather than two numbers side by side.
|
|
899
|
+
expect(result.exchanges.reduce((sum, one) => sum + one.price_difference, 0)).toBeCloseTo(
|
|
900
|
+
result.price_difference,
|
|
901
|
+
5,
|
|
902
|
+
);
|
|
903
|
+
|
|
904
|
+
// The stored fields keep tau2's shape: two sorted SETS, and the second one
|
|
905
|
+
// really is in the other order.
|
|
906
|
+
const order = retailSlot.get(ctx).store.orders["#W5866402"];
|
|
907
|
+
expect(order?.exchange_items).toEqual(["6242772310", "9727387530"]);
|
|
908
|
+
expect(order?.exchange_new_items).toEqual(["2509076505", "7407838442"]);
|
|
909
|
+
});
|
|
910
|
+
|
|
853
911
|
test("does NOT move the gift-card balance — it is a request, not a settlement", async () => {
|
|
854
912
|
const ctx = await authedCtx("aarav.anderson9752@example.com");
|
|
855
913
|
await exchangeDeliveredOrderItems.execute(
|
|
@@ -885,30 +943,34 @@ describe("exchange_delivered_order_items", () => {
|
|
|
885
943
|
|
|
886
944
|
test("exchanges both copies of a duplicate and doubles the difference", async () => {
|
|
887
945
|
const ctx = await authedCtx("aarav.anderson9752@example.com");
|
|
888
|
-
const result = (
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
946
|
+
const result = ok<ExchangeResult>(
|
|
947
|
+
await exchangeDeliveredOrderItems.execute(
|
|
948
|
+
{
|
|
949
|
+
order_id: "#W4316152",
|
|
950
|
+
item_ids: ["7292993796", "7292993796"],
|
|
951
|
+
new_item_ids: ["3909406921", "3909406921"],
|
|
952
|
+
payment_method_id: "gift_card_7245904",
|
|
953
|
+
},
|
|
954
|
+
ctx,
|
|
955
|
+
),
|
|
956
|
+
);
|
|
957
|
+
expect(result.price_difference).toBe(6.9);
|
|
898
958
|
});
|
|
899
959
|
|
|
900
960
|
test("records a negative difference as a refund direction", async () => {
|
|
901
961
|
const ctx = await authedCtx("olivia.ito5204@example.com");
|
|
902
|
-
const result = (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
962
|
+
const result = ok<ExchangeResult>(
|
|
963
|
+
await exchangeDeliveredOrderItems.execute(
|
|
964
|
+
{
|
|
965
|
+
order_id: "#W5866402",
|
|
966
|
+
item_ids: ["6242772310"],
|
|
967
|
+
new_item_ids: ["6200867091"],
|
|
968
|
+
payment_method_id: "gift_card_7794233",
|
|
969
|
+
},
|
|
970
|
+
ctx,
|
|
971
|
+
),
|
|
972
|
+
);
|
|
973
|
+
expect(result.price_difference).toBe(-40.86);
|
|
912
974
|
});
|
|
913
975
|
|
|
914
976
|
test("refuses a gift card that cannot cover the difference", async () => {
|
|
@@ -1019,11 +1081,13 @@ interface TransferResult {
|
|
|
1019
1081
|
|
|
1020
1082
|
describe("transfer_to_human_agents", () => {
|
|
1021
1083
|
test("works without authentication — the escape hatch cannot be gated", async () => {
|
|
1022
|
-
const result = (
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1084
|
+
const result = ok<TransferResult>(
|
|
1085
|
+
await transferToHumanAgents.execute(
|
|
1086
|
+
{ summary: "Caller wants to dispute a charge from 2019." },
|
|
1087
|
+
makeCtx(),
|
|
1088
|
+
),
|
|
1089
|
+
);
|
|
1026
1090
|
expect(isToolFailure(result)).toBe(false);
|
|
1027
|
-
expect(
|
|
1091
|
+
expect(result.transferred).toBe(true);
|
|
1028
1092
|
});
|
|
1029
1093
|
});
|