From ae112a5a492718f8e62fa9164875b64f1a04cbb8 Mon Sep 17 00:00:00 2001 From: Julian Sommer <115516918+juliansommer@users.noreply.github.com> Date: Mon, 24 Aug 2026 10:58:32 +1000 Subject: [PATCH] test(ui): type the query and SDK mock test helpers (#859) --- .../frontend/src/queries/admin.test.ts | 30 ++--- .../frontend/src/queries/ec2.test.ts | 124 ++++-------------- .../frontend/src/queries/eks.test.ts | 32 ++--- .../frontend/src/queries/elbv2.test.ts | 28 ++-- .../frontend/src/queries/iam.test.ts | 118 ++++------------- .../frontend/src/queries/rds.test.ts | 40 +++--- .../frontend/src/queries/s3.test.ts | 16 +-- .../create-load-balancer.test.tsx | 19 ++- .../describe-load-balancers/$id.test.tsx | 14 +- .../describe-load-balancers/index.test.tsx | 14 +- .../create-target-group.test.tsx | 19 ++- .../describe-target-groups/$id.test.tsx | 14 +- .../describe-target-groups/index.test.tsx | 39 +++--- .../ecs/(clusters)/create-service.test.tsx | 5 +- .../register-task-definition.test.tsx | 5 +- .../_auth/ecs/(clusters)/run-task.test.tsx | 5 +- .../frontend/src/test/elbv2-e2e.test.tsx | 2 +- .../spinifexui/frontend/src/test/query.ts | 39 ++++++ 18 files changed, 235 insertions(+), 328 deletions(-) create mode 100644 spinifex/services/spinifexui/frontend/src/test/query.ts diff --git a/spinifex/services/spinifexui/frontend/src/queries/admin.test.ts b/spinifex/services/spinifexui/frontend/src/queries/admin.test.ts index 04c951f42..c5b0dd455 100644 --- a/spinifex/services/spinifexui/frontend/src/queries/admin.test.ts +++ b/spinifex/services/spinifexui/frontend/src/queries/admin.test.ts @@ -10,6 +10,7 @@ vi.mock("@/lib/signed-fetch", () => ({ import { getCredentials } from "@/lib/auth" import { signedFetch } from "@/lib/signed-fetch" +import { callQueryFn } from "@/test/query" import { adminNodesQueryOptions, @@ -20,15 +21,6 @@ import { const mockGetCredentials = vi.mocked(getCredentials) const mockSignedFetch = vi.mocked(signedFetch) -function getQueryFn(opts: { - queryFn?: unknown -}): (ctx: never) => Promise { - if (typeof opts.queryFn !== "function") { - throw new TypeError("queryFn is not defined") - } - return opts.queryFn as (ctx: never) => Promise -} - describe("adminNodesQueryOptions", () => { it("has the correct query key", () => { expect(adminNodesQueryOptions.queryKey).toStrictEqual(["admin", "nodes"]) @@ -36,9 +28,9 @@ describe("adminNodesQueryOptions", () => { it("throws when not authenticated", async () => { mockGetCredentials.mockReturnValue(null) - await expect( - getQueryFn(adminNodesQueryOptions)({} as never), - ).rejects.toThrow("Not authenticated") + await expect(callQueryFn(adminNodesQueryOptions)).rejects.toThrow( + "Not authenticated", + ) }) it("calls signedFetch with GetNodes action", async () => { @@ -51,7 +43,7 @@ describe("adminNodesQueryOptions", () => { mockGetCredentials.mockReturnValue(creds) mockSignedFetch.mockResolvedValue({ nodes: [], cluster_mode: "single" }) - await getQueryFn(adminNodesQueryOptions)({} as never) + await callQueryFn(adminNodesQueryOptions) expect(mockSignedFetch).toHaveBeenCalledWith({ action: "GetNodes", @@ -67,7 +59,7 @@ describe("adminVMsQueryOptions", () => { it("throws when not authenticated", async () => { mockGetCredentials.mockReturnValue(null) - await expect(getQueryFn(adminVMsQueryOptions)({} as never)).rejects.toThrow( + await expect(callQueryFn(adminVMsQueryOptions)).rejects.toThrow( "Not authenticated", ) }) @@ -82,7 +74,7 @@ describe("adminVMsQueryOptions", () => { mockGetCredentials.mockReturnValue(creds) mockSignedFetch.mockResolvedValue({ vms: [] }) - await getQueryFn(adminVMsQueryOptions)({} as never) + await callQueryFn(adminVMsQueryOptions) expect(mockSignedFetch).toHaveBeenCalledWith({ action: "GetVMs", @@ -101,9 +93,9 @@ describe("adminStorageStatusQueryOptions", () => { it("throws when not authenticated", async () => { mockGetCredentials.mockReturnValue(null) - await expect( - getQueryFn(adminStorageStatusQueryOptions)({} as never), - ).rejects.toThrow("Not authenticated") + await expect(callQueryFn(adminStorageStatusQueryOptions)).rejects.toThrow( + "Not authenticated", + ) }) it("calls signedFetch with GetStorageStatus action", async () => { @@ -116,7 +108,7 @@ describe("adminStorageStatusQueryOptions", () => { mockGetCredentials.mockReturnValue(creds) mockSignedFetch.mockResolvedValue({}) - await getQueryFn(adminStorageStatusQueryOptions)({} as never) + await callQueryFn(adminStorageStatusQueryOptions) expect(mockSignedFetch).toHaveBeenCalledWith({ action: "GetStorageStatus", diff --git a/spinifex/services/spinifexui/frontend/src/queries/ec2.test.ts b/spinifex/services/spinifexui/frontend/src/queries/ec2.test.ts index b5dcb539b..04a4de3af 100644 --- a/spinifex/services/spinifexui/frontend/src/queries/ec2.test.ts +++ b/spinifex/services/spinifexui/frontend/src/queries/ec2.test.ts @@ -6,6 +6,8 @@ vi.mock("@/lib/awsClient", () => ({ getEc2Client: () => ({ send: mockSend }), })) +import { callQueryFn } from "@/test/query" + import { ec2AvailabilityZonesQueryOptions, ec2IamInstanceProfileAssociationsQueryOptions, @@ -225,17 +227,13 @@ describe("queryFn", () => { it("ec2ImageQueryOptions returns empty result for undefined imageId", async () => { const options = ec2ImageQueryOptions(undefined) - const queryFn = options.queryFn as (ctx: never) => Promise - const result = await queryFn({} as never) + const result = await callQueryFn(options) expect(result).toStrictEqual({ Images: [], $metadata: {} }) expect(mockSend).not.toHaveBeenCalled() }) it("ec2ImageQueryOptions sends DescribeImagesCommand for valid imageId", async () => { - const queryFn = ec2ImageQueryOptions("ami-123").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2ImageQueryOptions("ami-123")) expect(mockSend).toHaveBeenCalledOnce() expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ ImageIds: ["ami-123"], @@ -243,210 +241,142 @@ describe("queryFn", () => { }) it("ec2InstancesQueryOptions sends DescribeInstancesCommand", async () => { - const queryFn = ec2InstancesQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2InstancesQueryOptions) expect(mockSend).toHaveBeenCalledOnce() expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("ec2LaunchTemplatesQueryOptions sends DescribeLaunchTemplatesCommand", async () => { - const queryFn = ec2LaunchTemplatesQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2LaunchTemplatesQueryOptions) expect(mockSend).toHaveBeenCalledOnce() expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("ec2LaunchTemplateVersionsQueryOptions sends command with ID", async () => { - const queryFn = ec2LaunchTemplateVersionsQueryOptions("lt-123").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2LaunchTemplateVersionsQueryOptions("lt-123")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ LaunchTemplateId: "lt-123", }) }) it("ec2InstanceQueryOptions sends DescribeInstancesCommand with ID", async () => { - const queryFn = ec2InstanceQueryOptions("i-123").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2InstanceQueryOptions("i-123")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ InstanceIds: ["i-123"], }) }) it("ec2KeyPairsQueryOptions sends DescribeKeyPairsCommand", async () => { - const queryFn = ec2KeyPairsQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2KeyPairsQueryOptions) expect(mockSend).toHaveBeenCalledOnce() expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("ec2KeyPairQueryOptions sends DescribeKeyPairsCommand with ID", async () => { - const queryFn = ec2KeyPairQueryOptions("kp-abc").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2KeyPairQueryOptions("kp-abc")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ KeyPairIds: ["kp-abc"], }) }) it("ec2VolumesQueryOptions sends DescribeVolumesCommand", async () => { - const queryFn = ec2VolumesQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2VolumesQueryOptions) expect(mockSend).toHaveBeenCalledOnce() }) it("ec2VolumeQueryOptions sends DescribeVolumesCommand with ID", async () => { - const queryFn = ec2VolumeQueryOptions("vol-1").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2VolumeQueryOptions("vol-1")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ VolumeIds: ["vol-1"], }) }) it("ec2SnapshotsQueryOptions sends DescribeSnapshotsCommand", async () => { - const queryFn = ec2SnapshotsQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2SnapshotsQueryOptions) expect(mockSend).toHaveBeenCalledOnce() }) it("ec2SnapshotQueryOptions sends DescribeSnapshotsCommand with ID", async () => { - const queryFn = ec2SnapshotQueryOptions("snap-1").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2SnapshotQueryOptions("snap-1")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ SnapshotIds: ["snap-1"], }) }) it("ec2VpcsQueryOptions sends DescribeVpcsCommand", async () => { - const queryFn = ec2VpcsQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2VpcsQueryOptions) expect(mockSend).toHaveBeenCalledOnce() }) it("ec2VpcQueryOptions sends DescribeVpcsCommand with ID", async () => { - const queryFn = ec2VpcQueryOptions("vpc-1").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2VpcQueryOptions("vpc-1")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ VpcIds: ["vpc-1"], }) }) it("ec2SubnetsQueryOptions sends DescribeSubnetsCommand", async () => { - const queryFn = ec2SubnetsQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2SubnetsQueryOptions) expect(mockSend).toHaveBeenCalledOnce() }) it("ec2SubnetQueryOptions sends DescribeSubnetsCommand with ID", async () => { - const queryFn = ec2SubnetQueryOptions("subnet-1").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2SubnetQueryOptions("subnet-1")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ SubnetIds: ["subnet-1"], }) }) it("ec2AvailabilityZonesQueryOptions sends DescribeAvailabilityZonesCommand", async () => { - const queryFn = ec2AvailabilityZonesQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2AvailabilityZonesQueryOptions) expect(mockSend).toHaveBeenCalledOnce() }) it("ec2RegionsQueryOptions sends DescribeRegionsCommand", async () => { - const queryFn = ec2RegionsQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2RegionsQueryOptions) expect(mockSend).toHaveBeenCalledOnce() }) it("ec2ImagesQueryOptions sends DescribeImagesCommand", async () => { - const queryFn = ec2ImagesQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2ImagesQueryOptions) expect(mockSend).toHaveBeenCalledOnce() }) it("ec2InstanceTypesQueryOptions sends DescribeInstanceTypesCommand with filter", async () => { - const queryFn = ec2InstanceTypesQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2InstanceTypesQueryOptions) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ Filters: [{ Name: "capacity", Values: ["true"] }], }) }) it("ec2PlacementGroupsQueryOptions sends DescribePlacementGroupsCommand", async () => { - const queryFn = ec2PlacementGroupsQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2PlacementGroupsQueryOptions) expect(mockSend).toHaveBeenCalledOnce() expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("ec2PlacementGroupQueryOptions sends DescribePlacementGroupsCommand with ID", async () => { - const queryFn = ec2PlacementGroupQueryOptions("pg-123").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2PlacementGroupQueryOptions("pg-123")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ GroupIds: ["pg-123"], }) }) it("ec2SecurityGroupsQueryOptions sends DescribeSecurityGroupsCommand", async () => { - const queryFn = ec2SecurityGroupsQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2SecurityGroupsQueryOptions) expect(mockSend).toHaveBeenCalledOnce() expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("ec2SecurityGroupQueryOptions sends DescribeSecurityGroupsCommand with ID", async () => { - const queryFn = ec2SecurityGroupQueryOptions("sg-123").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(ec2SecurityGroupQueryOptions("sg-123")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ GroupIds: ["sg-123"], }) }) it("ec2IamInstanceProfileAssociationsQueryOptions filters by instance-id", async () => { - const queryFn = ec2IamInstanceProfileAssociationsQueryOptions("i-123") - .queryFn as (ctx: never) => Promise - await queryFn({} as never) + await callQueryFn(ec2IamInstanceProfileAssociationsQueryOptions("i-123")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ Filters: [{ Name: "instance-id", Values: ["i-123"] }], }) diff --git a/spinifex/services/spinifexui/frontend/src/queries/eks.test.ts b/spinifex/services/spinifexui/frontend/src/queries/eks.test.ts index b7e10707a..98179f05c 100644 --- a/spinifex/services/spinifexui/frontend/src/queries/eks.test.ts +++ b/spinifex/services/spinifexui/frontend/src/queries/eks.test.ts @@ -6,6 +6,8 @@ vi.mock("@/lib/awsClient", () => ({ getEksClient: () => ({ send: mockSend }), })) +import { callQueryFn } from "@/test/query" + import { eksAccessEntriesQueryOptions, eksAccessEntryQueryOptions, @@ -114,27 +116,18 @@ describe("queryFn", () => { }) it("clusters sends ListClustersCommand", async () => { - const queryFn = eksClustersQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(eksClustersQueryOptions) expect(mockSend).toHaveBeenCalledOnce() expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("cluster sends DescribeClusterCommand with name", async () => { - const queryFn = eksClusterQueryOptions("c1").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(eksClusterQueryOptions("c1")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ name: "c1" }) }) it("nodegroup sends DescribeNodegroupCommand", async () => { - const queryFn = eksNodegroupQueryOptions("c1", "ng1").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(eksNodegroupQueryOptions("c1", "ng1")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ clusterName: "c1", nodegroupName: "ng1", @@ -142,10 +135,7 @@ describe("queryFn", () => { }) it("access entry sends DescribeAccessEntryCommand", async () => { - const queryFn = eksAccessEntryQueryOptions("c1", "arn:p").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(eksAccessEntryQueryOptions("c1", "arn:p")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ clusterName: "c1", principalArn: "arn:p", @@ -153,20 +143,14 @@ describe("queryFn", () => { }) it("addons sends ListAddonsCommand with cluster", async () => { - const queryFn = eksAddonsQueryOptions("c1").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(eksAddonsQueryOptions("c1")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ clusterName: "c1", }) }) it("addon sends DescribeAddonCommand", async () => { - const queryFn = eksAddonQueryOptions("c1", "coredns").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(eksAddonQueryOptions("c1", "coredns")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ clusterName: "c1", addonName: "coredns", diff --git a/spinifex/services/spinifexui/frontend/src/queries/elbv2.test.ts b/spinifex/services/spinifexui/frontend/src/queries/elbv2.test.ts index 6246281e7..aceaa2cf2 100644 --- a/spinifex/services/spinifexui/frontend/src/queries/elbv2.test.ts +++ b/spinifex/services/spinifexui/frontend/src/queries/elbv2.test.ts @@ -6,6 +6,8 @@ vi.mock("@/lib/awsClient", () => ({ getElbv2Client: () => ({ send: mockSend }), })) +import { callQueryFn } from "@/test/query" + import { elbv2ListenersQueryOptions, elbv2LoadBalancerAttributesQueryOptions, @@ -88,25 +90,17 @@ describe("elbv2 query keys", () => { }) }) -type QueryFnWithSignal = (ctx: { signal: AbortSignal }) => Promise - -async function callQueryFn(queryFn: unknown): Promise { - return await (queryFn as QueryFnWithSignal)({ - signal: new AbortController().signal, - }) -} - describe("elbv2 implemented queries send the right command", () => { it("loadBalancers list sends DescribeLoadBalancersCommand", async () => { mockSend.mockResolvedValueOnce({ LoadBalancers: [] }) - await callQueryFn(elbv2LoadBalancersQueryOptions.queryFn) + await callQueryFn(elbv2LoadBalancersQueryOptions) expect(mockSend).toHaveBeenCalledOnce() expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("loadBalancer detail filters by ARN", async () => { mockSend.mockResolvedValueOnce({ LoadBalancers: [] }) - await callQueryFn(elbv2LoadBalancerQueryOptions("arn:lb").queryFn) + await callQueryFn(elbv2LoadBalancerQueryOptions("arn:lb")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ LoadBalancerArns: ["arn:lb"], }) @@ -114,7 +108,7 @@ describe("elbv2 implemented queries send the right command", () => { it("loadBalancer attributes sends arn", async () => { mockSend.mockResolvedValueOnce({ Attributes: [] }) - await callQueryFn(elbv2LoadBalancerAttributesQueryOptions("arn:lb").queryFn) + await callQueryFn(elbv2LoadBalancerAttributesQueryOptions("arn:lb")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ LoadBalancerArn: "arn:lb", }) @@ -122,7 +116,7 @@ describe("elbv2 implemented queries send the right command", () => { it("listeners filters by load balancer arn", async () => { mockSend.mockResolvedValueOnce({ Listeners: [] }) - await callQueryFn(elbv2ListenersQueryOptions("arn:lb").queryFn) + await callQueryFn(elbv2ListenersQueryOptions("arn:lb")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ LoadBalancerArn: "arn:lb", }) @@ -130,7 +124,7 @@ describe("elbv2 implemented queries send the right command", () => { it("tags sends resource arns", async () => { mockSend.mockResolvedValueOnce({ TagDescriptions: [] }) - await callQueryFn(elbv2TagsQueryOptions(["arn:lb"]).queryFn) + await callQueryFn(elbv2TagsQueryOptions(["arn:lb"])) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ ResourceArns: ["arn:lb"], }) @@ -138,13 +132,13 @@ describe("elbv2 implemented queries send the right command", () => { it("targetGroups list sends DescribeTargetGroupsCommand", async () => { mockSend.mockResolvedValueOnce({ TargetGroups: [] }) - await callQueryFn(elbv2TargetGroupsQueryOptions.queryFn) + await callQueryFn(elbv2TargetGroupsQueryOptions) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("targetGroup detail filters by ARN", async () => { mockSend.mockResolvedValueOnce({ TargetGroups: [] }) - await callQueryFn(elbv2TargetGroupQueryOptions("arn:tg").queryFn) + await callQueryFn(elbv2TargetGroupQueryOptions("arn:tg")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ TargetGroupArns: ["arn:tg"], }) @@ -152,7 +146,7 @@ describe("elbv2 implemented queries send the right command", () => { it("targetGroup attributes sends arn", async () => { mockSend.mockResolvedValueOnce({ Attributes: [] }) - await callQueryFn(elbv2TargetGroupAttributesQueryOptions("arn:tg").queryFn) + await callQueryFn(elbv2TargetGroupAttributesQueryOptions("arn:tg")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ TargetGroupArn: "arn:tg", }) @@ -160,7 +154,7 @@ describe("elbv2 implemented queries send the right command", () => { it("targetHealth sends target group arn", async () => { mockSend.mockResolvedValueOnce({ TargetHealthDescriptions: [] }) - await callQueryFn(elbv2TargetHealthQueryOptions("arn:tg").queryFn) + await callQueryFn(elbv2TargetHealthQueryOptions("arn:tg")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ TargetGroupArn: "arn:tg", }) diff --git a/spinifex/services/spinifexui/frontend/src/queries/iam.test.ts b/spinifex/services/spinifexui/frontend/src/queries/iam.test.ts index 311437bfe..047b80440 100644 --- a/spinifex/services/spinifexui/frontend/src/queries/iam.test.ts +++ b/spinifex/services/spinifexui/frontend/src/queries/iam.test.ts @@ -6,6 +6,8 @@ vi.mock("@/lib/awsClient", () => ({ getIamClient: () => ({ send: mockSend }), })) +import { callQueryFn } from "@/test/query" + import { iamAccessKeysQueryOptions, iamAttachedGroupPoliciesQueryOptions, @@ -225,57 +227,39 @@ describe("queryFn", () => { }) it("iamUsersQueryOptions sends ListUsersCommand", async () => { - const queryFn = iamUsersQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamUsersQueryOptions) expect(mockSend).toHaveBeenCalledOnce() expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("iamUserQueryOptions sends GetUserCommand with userName", async () => { - const queryFn = iamUserQueryOptions("admin").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamUserQueryOptions("admin")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ UserName: "admin", }) }) it("iamAccessKeysQueryOptions sends ListAccessKeysCommand", async () => { - const queryFn = iamAccessKeysQueryOptions("admin").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamAccessKeysQueryOptions("admin")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ UserName: "admin", }) }) it("iamPoliciesQueryOptions sends ListPoliciesCommand with Local scope", async () => { - const queryFn = iamPoliciesQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamPoliciesQueryOptions) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ Scope: "Local" }) }) it("iamPolicyQueryOptions sends GetPolicyCommand with policyArn", async () => { - const queryFn = iamPolicyQueryOptions("arn:test").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamPolicyQueryOptions("arn:test")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ PolicyArn: "arn:test", }) }) it("iamPolicyVersionQueryOptions sends GetPolicyVersionCommand", async () => { - const queryFn = iamPolicyVersionQueryOptions("arn:test", "v1").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamPolicyVersionQueryOptions("arn:test", "v1")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ PolicyArn: "arn:test", VersionId: "v1", @@ -283,112 +267,78 @@ describe("queryFn", () => { }) it("iamAttachedUserPoliciesQueryOptions sends ListAttachedUserPoliciesCommand", async () => { - const queryFn = iamAttachedUserPoliciesQueryOptions("admin").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamAttachedUserPoliciesQueryOptions("admin")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ UserName: "admin", }) }) it("iamRolesQueryOptions sends ListRolesCommand", async () => { - const queryFn = iamRolesQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamRolesQueryOptions) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("iamRoleQueryOptions sends GetRoleCommand with roleName", async () => { - const queryFn = iamRoleQueryOptions("my-role").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamRoleQueryOptions("my-role")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ RoleName: "my-role", }) }) it("iamAttachedRolePoliciesQueryOptions sends ListAttachedRolePoliciesCommand", async () => { - const queryFn = iamAttachedRolePoliciesQueryOptions("my-role").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamAttachedRolePoliciesQueryOptions("my-role")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ RoleName: "my-role", }) }) it("iamInstanceProfilesQueryOptions sends ListInstanceProfilesCommand", async () => { - const queryFn = iamInstanceProfilesQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamInstanceProfilesQueryOptions) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("iamInstanceProfileQueryOptions sends GetInstanceProfileCommand", async () => { - const queryFn = iamInstanceProfileQueryOptions("my-profile").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamInstanceProfileQueryOptions("my-profile")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ InstanceProfileName: "my-profile", }) }) it("iamInstanceProfilesForRoleQueryOptions sends ListInstanceProfilesForRoleCommand", async () => { - const queryFn = iamInstanceProfilesForRoleQueryOptions("my-role") - .queryFn as (ctx: never) => Promise - await queryFn({} as never) + await callQueryFn(iamInstanceProfilesForRoleQueryOptions("my-role")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ RoleName: "my-role", }) }) it("iamGroupsQueryOptions sends ListGroupsCommand", async () => { - const queryFn = iamGroupsQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamGroupsQueryOptions) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("iamGroupQueryOptions sends GetGroupCommand with groupName", async () => { - const queryFn = iamGroupQueryOptions("my-group").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamGroupQueryOptions("my-group")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ GroupName: "my-group", }) }) it("iamAttachedGroupPoliciesQueryOptions sends ListAttachedGroupPoliciesCommand", async () => { - const queryFn = iamAttachedGroupPoliciesQueryOptions("my-group") - .queryFn as (ctx: never) => Promise - await queryFn({} as never) + await callQueryFn(iamAttachedGroupPoliciesQueryOptions("my-group")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ GroupName: "my-group", }) }) it("iamGroupsForUserQueryOptions sends ListGroupsForUserCommand with userName", async () => { - const queryFn = iamGroupsForUserQueryOptions("admin").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamGroupsForUserQueryOptions("admin")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ UserName: "admin", }) }) it("iamUserPoliciesQueryOptions sends ListUserPoliciesCommand", async () => { - const queryFn = iamUserPoliciesQueryOptions("admin").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamUserPoliciesQueryOptions("admin")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ UserName: "admin", }) @@ -398,10 +348,9 @@ describe("queryFn", () => { mockSend.mockResolvedValueOnce({ PolicyDocument: encodeURIComponent('{"Version":"2012-10-17"}'), }) - const queryFn = iamUserPolicyQueryOptions("admin", "s3-read").queryFn as ( - ctx: never, - ) => Promise - const document = await queryFn({} as never) + const document = await callQueryFn( + iamUserPolicyQueryOptions("admin", "s3-read"), + ) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ UserName: "admin", PolicyName: "s3-read", @@ -410,10 +359,7 @@ describe("queryFn", () => { }) it("iamRolePoliciesQueryOptions sends ListRolePoliciesCommand", async () => { - const queryFn = iamRolePoliciesQueryOptions("my-role").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamRolePoliciesQueryOptions("my-role")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ RoleName: "my-role", }) @@ -423,10 +369,9 @@ describe("queryFn", () => { mockSend.mockResolvedValueOnce({ PolicyDocument: '{"Version":"2012-10-17"}', }) - const queryFn = iamRolePolicyQueryOptions("my-role", "s3-read").queryFn as ( - ctx: never, - ) => Promise - const document = await queryFn({} as never) + const document = await callQueryFn( + iamRolePolicyQueryOptions("my-role", "s3-read"), + ) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ RoleName: "my-role", PolicyName: "s3-read", @@ -435,19 +380,14 @@ describe("queryFn", () => { }) it("iamGroupPoliciesQueryOptions sends ListGroupPoliciesCommand", async () => { - const queryFn = iamGroupPoliciesQueryOptions("my-group").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(iamGroupPoliciesQueryOptions("my-group")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ GroupName: "my-group", }) }) it("iamGroupPolicyQueryOptions sends GetGroupPolicyCommand with names", async () => { - const queryFn = iamGroupPolicyQueryOptions("my-group", "s3-read") - .queryFn as (ctx: never) => Promise - await queryFn({} as never) + await callQueryFn(iamGroupPolicyQueryOptions("my-group", "s3-read")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ GroupName: "my-group", PolicyName: "s3-read", diff --git a/spinifex/services/spinifexui/frontend/src/queries/rds.test.ts b/spinifex/services/spinifexui/frontend/src/queries/rds.test.ts index 40a7825b6..30bd83b80 100644 --- a/spinifex/services/spinifexui/frontend/src/queries/rds.test.ts +++ b/spinifex/services/spinifexui/frontend/src/queries/rds.test.ts @@ -6,6 +6,8 @@ vi.mock("@/lib/awsClient", () => ({ getRdsClient: () => ({ send: mockSend }), })) +import { callQueryFn } from "@/test/query" + import { rdsAutomatedBackupsQueryOptions, rdsDBInstanceQueryOptions, @@ -148,25 +150,17 @@ describe("rds query keys", () => { }) }) -type QueryFnWithSignal = (ctx: { signal: AbortSignal }) => Promise - -async function callQueryFn(queryFn: unknown): Promise { - return await (queryFn as QueryFnWithSignal)({ - signal: new AbortController().signal, - }) -} - describe("rds queries send the right command", () => { it("dbInstances list sends an unfiltered describe", async () => { mockSend.mockResolvedValueOnce({ DBInstances: [] }) - await callQueryFn(rdsDBInstancesQueryOptions.queryFn) + await callQueryFn(rdsDBInstancesQueryOptions) expect(mockSend).toHaveBeenCalledOnce() expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("dbInstance detail filters by identifier", async () => { mockSend.mockResolvedValueOnce({ DBInstances: [] }) - await callQueryFn(rdsDBInstanceQueryOptions("orders-db").queryFn) + await callQueryFn(rdsDBInstanceQueryOptions("orders-db")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ DBInstanceIdentifier: "orders-db", }) @@ -174,7 +168,7 @@ describe("rds queries send the right command", () => { it("events asks for the whole 14-day ring, not the default hour", async () => { mockSend.mockResolvedValueOnce({ Events: [] }) - await callQueryFn(rdsEventsQueryOptions("orders-db").queryFn) + await callQueryFn(rdsEventsQueryOptions("orders-db")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ SourceIdentifier: "orders-db", SourceType: "db-instance", @@ -184,7 +178,7 @@ describe("rds queries send the right command", () => { it("tags sends the resource name", async () => { mockSend.mockResolvedValueOnce({ TagList: [] }) - await callQueryFn(rdsTagsQueryOptions("arn:db").queryFn) + await callQueryFn(rdsTagsQueryOptions("arn:db")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ ResourceName: "arn:db", }) @@ -192,7 +186,7 @@ describe("rds queries send the right command", () => { it("orderable options filter by engine", async () => { mockSend.mockResolvedValueOnce({ OrderableDBInstanceOptions: [] }) - await callQueryFn(rdsOrderableOptionsQueryOptions("mariadb").queryFn) + await callQueryFn(rdsOrderableOptionsQueryOptions("mariadb")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ Engine: "mariadb", }) @@ -200,18 +194,18 @@ describe("rds queries send the right command", () => { it("subnet groups and parameter groups send unfiltered describes", async () => { mockSend.mockResolvedValueOnce({ DBSubnetGroups: [] }) - await callQueryFn(rdsSubnetGroupsQueryOptions.queryFn) + await callQueryFn(rdsSubnetGroupsQueryOptions) mockSend.mockResolvedValueOnce({ DBParameterGroups: [] }) - await callQueryFn(rdsParameterGroupsQueryOptions.queryFn) + await callQueryFn(rdsParameterGroupsQueryOptions) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) expect(mockSend.mock.calls[1]?.[0].input).toStrictEqual({}) }) it("the single group describes filter by name", async () => { mockSend.mockResolvedValueOnce({ DBSubnetGroups: [] }) - await callQueryFn(rdsSubnetGroupQueryOptions("orders-subnets").queryFn) + await callQueryFn(rdsSubnetGroupQueryOptions("orders-subnets")) mockSend.mockResolvedValueOnce({ DBParameterGroups: [] }) - await callQueryFn(rdsParameterGroupQueryOptions("orders-pg").queryFn) + await callQueryFn(rdsParameterGroupQueryOptions("orders-pg")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ DBSubnetGroupName: "orders-subnets", }) @@ -222,7 +216,7 @@ describe("rds queries send the right command", () => { it("snapshot events ask the db-snapshot ring", async () => { mockSend.mockResolvedValueOnce({ Events: [] }) - await callQueryFn(rdsSnapshotEventsQueryOptions("orders-snap").queryFn) + await callQueryFn(rdsSnapshotEventsQueryOptions("orders-snap")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ SourceIdentifier: "orders-snap", SourceType: "db-snapshot", @@ -232,11 +226,11 @@ describe("rds queries send the right command", () => { it("the snapshot describes filter by snapshot and by instance", async () => { mockSend.mockResolvedValueOnce({ DBSnapshots: [] }) - await callQueryFn(rdsDBSnapshotsQueryOptions.queryFn) + await callQueryFn(rdsDBSnapshotsQueryOptions) mockSend.mockResolvedValueOnce({ DBSnapshots: [] }) - await callQueryFn(rdsDBSnapshotQueryOptions("orders-snap").queryFn) + await callQueryFn(rdsDBSnapshotQueryOptions("orders-snap")) mockSend.mockResolvedValueOnce({ DBSnapshots: [] }) - await callQueryFn(rdsInstanceDBSnapshotsQueryOptions("orders-db").queryFn) + await callQueryFn(rdsInstanceDBSnapshotsQueryOptions("orders-db")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) expect(mockSend.mock.calls[1]?.[0].input).toStrictEqual({ DBSnapshotIdentifier: "orders-snap", @@ -248,7 +242,7 @@ describe("rds queries send the right command", () => { it("automated backups filter by instance", async () => { mockSend.mockResolvedValueOnce({ DBInstanceAutomatedBackups: [] }) - await callQueryFn(rdsAutomatedBackupsQueryOptions("orders-db").queryFn) + await callQueryFn(rdsAutomatedBackupsQueryOptions("orders-db")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ DBInstanceIdentifier: "orders-db", }) @@ -258,7 +252,7 @@ describe("rds queries send the right command", () => { // set is one round trip and the split is a client-side filter. it("parameters are fetched unfiltered by source", async () => { mockSend.mockResolvedValueOnce({ Parameters: [] }) - await callQueryFn(rdsParametersQueryOptions("orders-pg").queryFn) + await callQueryFn(rdsParametersQueryOptions("orders-pg")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ DBParameterGroupName: "orders-pg", }) diff --git a/spinifex/services/spinifexui/frontend/src/queries/s3.test.ts b/spinifex/services/spinifexui/frontend/src/queries/s3.test.ts index 2c1423af2..560f4644b 100644 --- a/spinifex/services/spinifexui/frontend/src/queries/s3.test.ts +++ b/spinifex/services/spinifexui/frontend/src/queries/s3.test.ts @@ -6,6 +6,8 @@ vi.mock("@/lib/awsClient", () => ({ getS3Client: () => ({ send: mockSend }), })) +import { callQueryFn } from "@/test/query" + import { s3BucketObjectsQueryOptions, s3BucketsQueryOptions } from "./s3" describe("query keys", () => { @@ -36,19 +38,13 @@ describe("queryFn", () => { }) it("s3BucketsQueryOptions sends ListBucketsCommand", async () => { - const queryFn = s3BucketsQueryOptions.queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(s3BucketsQueryOptions) expect(mockSend).toHaveBeenCalledOnce() expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({}) }) it("s3BucketObjectsQueryOptions sends ListObjectsV2Command with bucket and delimiter", async () => { - const queryFn = s3BucketObjectsQueryOptions("my-bucket").queryFn as ( - ctx: never, - ) => Promise - await queryFn({} as never) + await callQueryFn(s3BucketObjectsQueryOptions("my-bucket")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ Bucket: "my-bucket", Prefix: undefined, @@ -57,9 +53,7 @@ describe("queryFn", () => { }) it("s3BucketObjectsQueryOptions sends prefix when provided", async () => { - const queryFn = s3BucketObjectsQueryOptions("my-bucket", "docs/") - .queryFn as (ctx: never) => Promise - await queryFn({} as never) + await callQueryFn(s3BucketObjectsQueryOptions("my-bucket", "docs/")) expect(mockSend.mock.calls[0]?.[0].input).toStrictEqual({ Bucket: "my-bucket", Prefix: "docs/", diff --git a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/create-load-balancer.test.tsx b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/create-load-balancer.test.tsx index f5bd31630..e26655868 100644 --- a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/create-load-balancer.test.tsx +++ b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/create-load-balancer.test.tsx @@ -14,7 +14,8 @@ const { routerState, sdk } = vi.hoisted(() => { readonly constructor: { name: string } readonly input: unknown } - const handlers = new Map unknown>() + type SdkHandler = (input: never) => unknown + const handlers = new Map() const send = vi.fn(async (command: Command): Promise => { const handler = handlers.get(command.constructor.name) if (!handler) { @@ -22,7 +23,7 @@ const { routerState, sdk } = vi.hoisted(() => { `No handler registered for SDK command ${command.constructor.name}`, ) } - return handler(command.input) + return handler(command.input as never) }) return { routerState: { @@ -31,7 +32,9 @@ const { routerState, sdk } = vi.hoisted(() => { }, sdk: { send, - setHandler: (name: string, handler: (input: unknown) => unknown) => { + // Handlers are keyed by command name, so each one declares the input type + // of its own command; the shared registry cannot name them all. + setHandler: (name: string, handler: SdkHandler) => { handlers.set(name, handler) }, reset: () => { @@ -51,10 +54,12 @@ vi.mock("@tanstack/react-router", async (importOriginal) => { const actual = await importOriginal() return { ...actual, - createFileRoute: () => (options: Record) => ({ - ...options, - useParams: () => routerState.params, - }), + createFileRoute: + () => + (options: TOptions) => ({ + ...options, + useParams: () => routerState.params, + }), useNavigate: () => routerState.navigate, Link: ({ children, to }: { children: React.ReactNode; to?: string }) => ( {children} diff --git a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/describe-load-balancers/$id.test.tsx b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/describe-load-balancers/$id.test.tsx index df239406b..43550d8ed 100644 --- a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/describe-load-balancers/$id.test.tsx +++ b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/describe-load-balancers/$id.test.tsx @@ -11,7 +11,8 @@ const { routerState, sdk } = vi.hoisted(() => { readonly constructor: { name: string } readonly input: unknown } - const handlers = new Map unknown>() + type SdkHandler = (input: never) => unknown + const handlers = new Map() const send = vi.fn(async (command: Command): Promise => { const handler = handlers.get(command.constructor.name) if (!handler) { @@ -19,13 +20,15 @@ const { routerState, sdk } = vi.hoisted(() => { `No handler registered for SDK command ${command.constructor.name}`, ) } - return handler(command.input) + return handler(command.input as never) }) return { routerState: { navigate: vi.fn() }, sdk: { send, - setHandler: (name: string, handler: (input: unknown) => unknown) => { + // Handlers are keyed by command name, so each one declares the input type + // of its own command; the shared registry cannot name them all. + setHandler: (name: string, handler: SdkHandler) => { handlers.set(name, handler) }, reset: () => { @@ -45,7 +48,10 @@ vi.mock("@tanstack/react-router", async (importOriginal) => { const actual = await importOriginal() return { ...actual, - createFileRoute: () => (options: Record) => options, + createFileRoute: + () => + (options: TOptions): TOptions => + options, useNavigate: () => routerState.navigate, Link: ({ children, diff --git a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/describe-load-balancers/index.test.tsx b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/describe-load-balancers/index.test.tsx index 477808570..c59676d83 100644 --- a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/describe-load-balancers/index.test.tsx +++ b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(load-balancers)/describe-load-balancers/index.test.tsx @@ -12,7 +12,8 @@ const { routerState, sdk } = vi.hoisted(() => { readonly constructor: { name: string } readonly input: unknown } - const handlers = new Map unknown>() + type SdkHandler = (input: never) => unknown + const handlers = new Map() const send = vi.fn(async (command: Command): Promise => { const handler = handlers.get(command.constructor.name) if (!handler) { @@ -20,13 +21,15 @@ const { routerState, sdk } = vi.hoisted(() => { `No handler registered for SDK command ${command.constructor.name}`, ) } - return handler(command.input) + return handler(command.input as never) }) return { routerState: { navigate: vi.fn() }, sdk: { send, - setHandler: (name: string, handler: (input: unknown) => unknown) => { + // Handlers are keyed by command name, so each one declares the input type + // of its own command; the shared registry cannot name them all. + setHandler: (name: string, handler: SdkHandler) => { handlers.set(name, handler) }, reset: () => { @@ -46,7 +49,10 @@ vi.mock("@tanstack/react-router", async (importOriginal) => { const actual = await importOriginal() return { ...actual, - createFileRoute: () => (options: Record) => options, + createFileRoute: + () => + (options: TOptions): TOptions => + options, useNavigate: () => routerState.navigate, Link: ({ children, diff --git a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/create-target-group.test.tsx b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/create-target-group.test.tsx index 5bc7ba5a0..1bf2fd7d5 100644 --- a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/create-target-group.test.tsx +++ b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/create-target-group.test.tsx @@ -13,7 +13,8 @@ const { routerState, sdk } = vi.hoisted(() => { readonly constructor: { name: string } readonly input: unknown } - const handlers = new Map unknown>() + type SdkHandler = (input: never) => unknown + const handlers = new Map() const send = vi.fn(async (command: Command): Promise => { const handler = handlers.get(command.constructor.name) if (!handler) { @@ -21,7 +22,7 @@ const { routerState, sdk } = vi.hoisted(() => { `No handler registered for SDK command ${command.constructor.name}`, ) } - return handler(command.input) + return handler(command.input as never) }) return { routerState: { @@ -30,7 +31,9 @@ const { routerState, sdk } = vi.hoisted(() => { }, sdk: { send, - setHandler: (name: string, handler: (input: unknown) => unknown) => { + // Handlers are keyed by command name, so each one declares the input type + // of its own command; the shared registry cannot name them all. + setHandler: (name: string, handler: SdkHandler) => { handlers.set(name, handler) }, reset: () => { @@ -50,10 +53,12 @@ vi.mock("@tanstack/react-router", async (importOriginal) => { const actual = await importOriginal() return { ...actual, - createFileRoute: () => (options: Record) => ({ - ...options, - useParams: () => routerState.params, - }), + createFileRoute: + () => + (options: TOptions) => ({ + ...options, + useParams: () => routerState.params, + }), useNavigate: () => routerState.navigate, Link: ({ children, to }: { children: React.ReactNode; to?: string }) => ( {children} diff --git a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/describe-target-groups/$id.test.tsx b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/describe-target-groups/$id.test.tsx index d89231703..5bf8379a0 100644 --- a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/describe-target-groups/$id.test.tsx +++ b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/describe-target-groups/$id.test.tsx @@ -11,7 +11,8 @@ const { routerState, sdk } = vi.hoisted(() => { readonly constructor: { name: string } readonly input: unknown } - const handlers = new Map unknown>() + type SdkHandler = (input: never) => unknown + const handlers = new Map() const send = vi.fn(async (command: Command): Promise => { const handler = handlers.get(command.constructor.name) if (!handler) { @@ -19,13 +20,15 @@ const { routerState, sdk } = vi.hoisted(() => { `No handler registered for SDK command ${command.constructor.name}`, ) } - return handler(command.input) + return handler(command.input as never) }) return { routerState: { navigate: vi.fn() }, sdk: { send, - setHandler: (name: string, handler: (input: unknown) => unknown) => { + // Handlers are keyed by command name, so each one declares the input type + // of its own command; the shared registry cannot name them all. + setHandler: (name: string, handler: SdkHandler) => { handlers.set(name, handler) }, reset: () => { @@ -45,7 +48,10 @@ vi.mock("@tanstack/react-router", async (importOriginal) => { const actual = await importOriginal() return { ...actual, - createFileRoute: () => (options: Record) => options, + createFileRoute: + () => + (options: TOptions): TOptions => + options, useNavigate: () => routerState.navigate, Link: ({ children, diff --git a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/describe-target-groups/index.test.tsx b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/describe-target-groups/index.test.tsx index b5299bba4..e14d44ae8 100644 --- a/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/describe-target-groups/index.test.tsx +++ b/spinifex/services/spinifexui/frontend/src/routes/_auth/ec2/(target-groups)/describe-target-groups/index.test.tsx @@ -12,7 +12,8 @@ const { routerState, sdk } = vi.hoisted(() => { readonly constructor: { name: string } readonly input: unknown } - const handlers = new Map unknown>() + type SdkHandler = (input: never) => unknown + const handlers = new Map() const send = vi.fn(async (command: Command): Promise => { const handler = handlers.get(command.constructor.name) if (!handler) { @@ -20,13 +21,15 @@ const { routerState, sdk } = vi.hoisted(() => { `No handler registered for SDK command ${command.constructor.name}`, ) } - return handler(command.input) + return handler(command.input as never) }) return { routerState: { navigate: vi.fn() }, sdk: { send, - setHandler: (name: string, handler: (input: unknown) => unknown) => { + // Handlers are keyed by command name, so each one declares the input type + // of its own command; the shared registry cannot name them all. + setHandler: (name: string, handler: SdkHandler) => { handlers.set(name, handler) }, reset: () => { @@ -46,7 +49,10 @@ vi.mock("@tanstack/react-router", async (importOriginal) => { const actual = await importOriginal() return { ...actual, - createFileRoute: () => (options: Record) => options, + createFileRoute: + () => + (options: TOptions): TOptions => + options, useNavigate: () => routerState.navigate, Link: ({ children, @@ -90,18 +96,21 @@ describe("describe-target-groups list route", () => { afterEach(() => vi.clearAllMocks()) it("renders target-group rows with health summaries from the mocked SDK", async () => { - sdk.setHandler("DescribeTargetHealthCommand", (input) => { - const arn = (input as { TargetGroupArn: string }).TargetGroupArn - if (arn === "arn:tg:1") { - return { - TargetHealthDescriptions: [ - { TargetHealth: { State: "healthy" } }, - { TargetHealth: { State: "unhealthy" } }, - ], + sdk.setHandler( + "DescribeTargetHealthCommand", + (input: { TargetGroupArn: string }) => { + const arn = input.TargetGroupArn + if (arn === "arn:tg:1") { + return { + TargetHealthDescriptions: [ + { TargetHealth: { State: "healthy" } }, + { TargetHealth: { State: "unhealthy" } }, + ], + } } - } - return { TargetHealthDescriptions: [] } - }) + return { TargetHealthDescriptions: [] } + }, + ) const qc = createTestQueryClient() qc.setQueryData(["elbv2", "targetGroups"], { TargetGroups: TGS }) diff --git a/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/create-service.test.tsx b/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/create-service.test.tsx index 885f3ee02..2144d67c3 100644 --- a/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/create-service.test.tsx +++ b/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/create-service.test.tsx @@ -12,13 +12,14 @@ const { routerState, sdk } = vi.hoisted(() => { readonly constructor: { name: string } readonly input: unknown } - const handlers = new Map unknown>() + type SdkHandler = (input: never) => unknown + const handlers = new Map() const send = vi.fn(async (command: Command): Promise => { const handler = handlers.get(command.constructor.name) if (!handler) { throw new Error(`No handler for ${command.constructor.name}`) } - return handler(command.input) + return handler(command.input as never) }) return { routerState: { navigate: vi.fn() }, diff --git a/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/register-task-definition.test.tsx b/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/register-task-definition.test.tsx index e5f5e5b05..f8e1895d6 100644 --- a/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/register-task-definition.test.tsx +++ b/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/register-task-definition.test.tsx @@ -12,13 +12,14 @@ const { routerState, sdk } = vi.hoisted(() => { readonly constructor: { name: string } readonly input: unknown } - const handlers = new Map unknown>() + type SdkHandler = (input: never) => unknown + const handlers = new Map() const send = vi.fn(async (command: Command): Promise => { const handler = handlers.get(command.constructor.name) if (!handler) { throw new Error(`No handler for ${command.constructor.name}`) } - return handler(command.input) + return handler(command.input as never) }) return { routerState: { navigate: vi.fn() }, diff --git a/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/run-task.test.tsx b/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/run-task.test.tsx index ffd20f21a..e330c67c2 100644 --- a/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/run-task.test.tsx +++ b/spinifex/services/spinifexui/frontend/src/routes/_auth/ecs/(clusters)/run-task.test.tsx @@ -12,13 +12,14 @@ const { routerState, sdk } = vi.hoisted(() => { readonly constructor: { name: string } readonly input: unknown } - const handlers = new Map unknown>() + type SdkHandler = (input: never) => unknown + const handlers = new Map() const send = vi.fn(async (command: Command): Promise => { const handler = handlers.get(command.constructor.name) if (!handler) { throw new Error(`No handler for ${command.constructor.name}`) } - return handler(command.input) + return handler(command.input as never) }) return { routerState: { navigate: vi.fn() }, diff --git a/spinifex/services/spinifexui/frontend/src/test/elbv2-e2e.test.tsx b/spinifex/services/spinifexui/frontend/src/test/elbv2-e2e.test.tsx index cf3987086..5cfa897a2 100644 --- a/spinifex/services/spinifexui/frontend/src/test/elbv2-e2e.test.tsx +++ b/spinifex/services/spinifexui/frontend/src/test/elbv2-e2e.test.tsx @@ -32,7 +32,7 @@ const { sdk } = vi.hoisted(() => { } interface Command { readonly constructor: { name: string } - readonly input: unknown + readonly input: object } const state = { diff --git a/spinifex/services/spinifexui/frontend/src/test/query.ts b/spinifex/services/spinifexui/frontend/src/test/query.ts new file mode 100644 index 000000000..559c78402 --- /dev/null +++ b/spinifex/services/spinifexui/frontend/src/test/query.ts @@ -0,0 +1,39 @@ +import { + QueryClient, + type QueryFunction, + type QueryKey, + type SkipToken, + skipToken, +} from "@tanstack/react-query" + +interface CallableQueryOptions { + queryKey: TQueryKey + queryFn?: QueryFunction | SkipToken +} + +function createQueryClient() { + return new QueryClient({ + defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, + }) +} + +// Calls a query's fetcher with a real context, so a query that starts reading +// signal or client fails loudly here instead of silently taking another branch. +// The return type is preserved, so callers can assert on the response. +export async function callQueryFn( + options: CallableQueryOptions, + client: QueryClient = createQueryClient(), +): Promise { + const { queryFn, queryKey } = options + if (queryFn === undefined || queryFn === skipToken) { + throw new TypeError( + `query options ${JSON.stringify(queryKey)} have no callable queryFn`, + ) + } + return await queryFn({ + client, + meta: undefined, + queryKey, + signal: new AbortController().signal, + }) +}