Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions __tests__/hooks/useAnalyticsQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { renderHook, act, waitFor } from "@testing-library/react-native";

/* ---- Mock useClient from SDK ---- */
const mockAnalyticsQuery = jest.fn();
const mockAnalyticsExplain = jest.fn();

const mockClient = {
analytics: {
query: mockAnalyticsQuery,
explain: mockAnalyticsExplain,
},
};

Expand All @@ -21,6 +23,7 @@ import { useAnalyticsQuery } from "~/hooks/useAnalyticsQuery";

beforeEach(() => {
mockAnalyticsQuery.mockReset();
mockAnalyticsExplain.mockReset();
});

describe("useAnalyticsQuery", () => {
Expand Down Expand Up @@ -152,4 +155,64 @@ describe("useAnalyticsQuery", () => {
limit: 10,
});
});

it("calls explain with current query params", async () => {
mockAnalyticsQuery.mockResolvedValue({ data: [], total: 0 });
mockAnalyticsExplain.mockResolvedValue({
sql: "SELECT status, COUNT(*) FROM tasks GROUP BY status",
plan: "Seq Scan on tasks",
description: "Count tasks grouped by status",
});

const { result } = renderHook(() =>
useAnalyticsQuery({ metric: "tasks", groupBy: "status", aggregate: "count" }),
);

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

let explainResult: unknown;
await act(async () => {
explainResult = await result.current.explain();
});

expect(mockAnalyticsExplain).toHaveBeenCalledWith({
metric: "tasks",
groupBy: "status",
aggregate: "count",
field: undefined,
filter: undefined,
startDate: undefined,
endDate: undefined,
limit: undefined,
});
expect(explainResult).toEqual({
sql: "SELECT status, COUNT(*) FROM tasks GROUP BY status",
plan: "Seq Scan on tasks",
description: "Count tasks grouped by status",
});
});

it("calls explain with custom payload", async () => {
mockAnalyticsQuery.mockResolvedValue({ data: [], total: 0 });
mockAnalyticsExplain.mockResolvedValue({ sql: "SELECT 1" });

const { result } = renderHook(() =>
useAnalyticsQuery({ metric: "tasks" }),
);

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

await act(async () => {
await result.current.explain({ metric: "custom", limit: 5 });
});

expect(mockAnalyticsExplain).toHaveBeenCalledWith({
metric: "custom",
limit: 5,
});
});
});
147 changes: 147 additions & 0 deletions __tests__/hooks/useAutomation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* Tests for useAutomation – validates automation trigger
* and approval/rejection operations.
*/
import { renderHook, act } from "@testing-library/react-native";

/* ---- Mock useClient from SDK ---- */
const mockTrigger = jest.fn();
const mockApprove = jest.fn();
const mockReject = jest.fn();

const mockClient = {
automation: { trigger: mockTrigger },
workflow: { approve: mockApprove, reject: mockReject },
};

jest.mock("@objectstack/client-react", () => ({
useClient: () => mockClient,
}));

import { useAutomation } from "~/hooks/useAutomation";

beforeEach(() => {
mockTrigger.mockReset();
mockApprove.mockReset();
mockReject.mockReset();
});

describe("useAutomation", () => {
it("triggers an automation flow with payload", async () => {
mockTrigger.mockResolvedValue({
executionId: "exec-1",
message: "Started",
data: { status: "ok" },
});

const { result } = renderHook(() => useAutomation());

let triggerResult: unknown;
await act(async () => {
triggerResult = await result.current.trigger("onboard-user", {
userId: "123",
});
});

expect(mockTrigger).toHaveBeenCalledWith("onboard-user", {
userId: "123",
});
expect(triggerResult).toEqual({
success: true,
executionId: "exec-1",
message: "Started",
data: { status: "ok" },
});
expect(result.current.isLoading).toBe(false);
expect(result.current.error).toBeNull();
});

it("triggers without payload", async () => {
mockTrigger.mockResolvedValue({});

const { result } = renderHook(() => useAutomation());

await act(async () => {
await result.current.trigger("daily-report");
});

expect(mockTrigger).toHaveBeenCalledWith("daily-report", {});
});

it("handles trigger error", async () => {
mockTrigger.mockRejectedValue(new Error("Flow not found"));

const { result } = renderHook(() => useAutomation());

await act(async () => {
await expect(
result.current.trigger("nonexistent"),
).rejects.toThrow("Flow not found");
});

expect(result.current.error?.message).toBe("Flow not found");
});

it("approves a workflow step", async () => {
mockApprove.mockResolvedValue({ success: true });

const { result } = renderHook(() => useAutomation());

await act(async () => {
await result.current.approve("tasks", "rec-1", "LGTM");
});

expect(mockApprove).toHaveBeenCalledWith({
object: "tasks",
recordId: "rec-1",
comment: "LGTM",
});
expect(result.current.error).toBeNull();
});

it("handles approval error", async () => {
mockApprove.mockRejectedValue(new Error("Not authorized"));

const { result } = renderHook(() => useAutomation());

await act(async () => {
await expect(
result.current.approve("tasks", "rec-1"),
).rejects.toThrow("Not authorized");
});

expect(result.current.error?.message).toBe("Not authorized");
});

it("rejects a workflow step with reason", async () => {
mockReject.mockResolvedValue({ success: true });

const { result } = renderHook(() => useAutomation());

await act(async () => {
await result.current.reject("tasks", "rec-1", "Needs changes", "See comments");
});

expect(mockReject).toHaveBeenCalledWith({
object: "tasks",
recordId: "rec-1",
reason: "Needs changes",
comment: "See comments",
});
expect(result.current.error).toBeNull();
});

it("handles rejection error", async () => {
mockReject.mockRejectedValue(new Error("Rejection failed"));

const { result } = renderHook(() => useAutomation());

await act(async () => {
await expect(
result.current.reject("tasks", "rec-1", "Bad"),
).rejects.toThrow("Rejection failed");
});

expect(result.current.error?.message).toBe("Rejection failed");
});
});
Loading
Loading