forked from firecrawl/firecrawl-convex
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexample.test.ts
More file actions
151 lines (129 loc) · 4.62 KB
/
Copy pathexample.test.ts
File metadata and controls
151 lines (129 loc) · 4.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/// <reference types="vite/client" />
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { api } from "./_generated/api.js";
import { initConvexTest, mockFetch } from "./setup.test.js";
const alice = { subject: "alice", issuer: "https://example.test" };
const bob = { subject: "bob", issuer: "https://example.test" };
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
vi.unstubAllGlobals();
});
describe("example app", () => {
test("scrapePage returns markdown", async () => {
const t = initConvexTest().withIdentity(alice);
mockFetch([{ success: true, data: { markdown: "# Docs" } }]);
expect(
await t.action(api.example.scrapePage, { url: "https://a.com" }),
).toEqual({ markdown: "# Docs" });
});
test("a crawl reports progress and calls onComplete when it finishes", async () => {
const t = initConvexTest().withIdentity(alice);
mockFetch([
{ success: true, id: "job-1", url: "https://a.com" },
{
success: true,
status: "completed",
total: 1,
completed: 1,
creditsUsed: 1,
data: [
{
markdown: "# One",
metadata: { url: "https://a.com/one", statusCode: 200 },
},
],
},
]);
const { crawlId } = await t.action(api.example.startCrawl, {
url: "https://a.com",
limit: 1,
label: "docs",
});
expect(await t.query(api.example.crawlProgress, { crawlId })).toMatchObject({
status: "scraping",
});
await t.finishAllScheduledFunctions(vi.runAllTimers);
expect(await t.query(api.example.crawlProgress, { crawlId })).toMatchObject({
status: "completed",
pageCount: 1,
});
const pages = await t.query(api.example.crawlPages, {
crawlId,
paginationOpts: { numItems: 10, cursor: null },
});
expect(pages.page).toHaveLength(1);
expect(pages.page[0].url).toBe("https://a.com/one");
// The onComplete callback wrote an app-owned row, with our context intact.
const reports = await t.query(api.example.reports, {});
expect(reports).toHaveLength(1);
expect(reports[0]).toMatchObject({
crawlId,
userId: "alice",
label: "docs",
status: "completed",
pageCount: 1,
});
});
});
describe("authorization", () => {
test("paid endpoints reject anonymous callers", async () => {
const t = initConvexTest();
const calls = mockFetch([{ success: true, data: { markdown: "# nope" } }]);
await expect(
t.action(api.example.scrapePage, { url: "https://a.com" }),
).rejects.toThrow(/Not signed in/);
await expect(
t.action(api.example.searchWeb, { query: "anything" }),
).rejects.toThrow(/Not signed in/);
await expect(
t.action(api.example.startCrawl, { url: "https://a.com" }),
).rejects.toThrow(/Not signed in/);
// No credits were spent on the rejected calls.
expect(calls).toHaveLength(0);
});
test("one user cannot read, cancel, or delete another's crawl", async () => {
const t = initConvexTest();
mockFetch([
{ success: true, id: "job-1", url: "https://a.com" },
{ success: true, status: "scraping", total: 1, completed: 0, data: [] },
]);
const { crawlId } = await t
.withIdentity(alice)
.action(api.example.startCrawl, { url: "https://a.com", limit: 1 });
const asBob = t.withIdentity(bob);
await expect(
asBob.query(api.example.crawlProgress, { crawlId }),
).rejects.toThrow(/No such crawl/);
await expect(
asBob.query(api.example.crawlPages, {
crawlId,
paginationOpts: { numItems: 10, cursor: null },
}),
).rejects.toThrow(/No such crawl/);
await expect(
asBob.action(api.example.cancelCrawl, { crawlId }),
).rejects.toThrow(/No such crawl/);
await expect(
asBob.mutation(api.example.deleteCrawl, { crawlId }),
).rejects.toThrow(/No such crawl/);
// Alice still can.
expect(
await t.withIdentity(alice).query(api.example.crawlProgress, { crawlId }),
).toMatchObject({ url: "https://a.com" });
});
test("listings are scoped to the caller", async () => {
const t = initConvexTest();
mockFetch([
{ success: true, id: "job-1", url: "https://alice.test" },
{ success: true, status: "scraping", total: 1, completed: 0, data: [] },
]);
await t
.withIdentity(alice)
.action(api.example.startCrawl, { url: "https://alice.test", limit: 1 });
expect(await t.withIdentity(alice).query(api.example.myCrawls, {})).toHaveLength(1);
expect(await t.withIdentity(bob).query(api.example.myCrawls, {})).toHaveLength(0);
});
});