@oh-my-pi/pi-coding-agent 5.7.67 → 5.7.69

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +6 -6
  3. package/package.json +8 -7
  4. package/src/migrations.ts +1 -34
  5. package/src/vendor/photon/index.js +4 -2
  6. package/src/vendor/photon/photon_rs_bg.wasm.b64.js +1 -0
  7. package/src/core/python-executor-display.test.ts +0 -42
  8. package/src/core/python-executor-lifecycle.test.ts +0 -99
  9. package/src/core/python-executor-mapping.test.ts +0 -41
  10. package/src/core/python-executor-per-call.test.ts +0 -49
  11. package/src/core/python-executor-session.test.ts +0 -103
  12. package/src/core/python-executor-streaming.test.ts +0 -77
  13. package/src/core/python-executor-timeout.test.ts +0 -35
  14. package/src/core/python-executor.lifecycle.test.ts +0 -139
  15. package/src/core/python-executor.result.test.ts +0 -49
  16. package/src/core/python-executor.test.ts +0 -180
  17. package/src/core/python-kernel-display.test.ts +0 -54
  18. package/src/core/python-kernel-env.test.ts +0 -138
  19. package/src/core/python-kernel-session.test.ts +0 -87
  20. package/src/core/python-kernel-ws.test.ts +0 -104
  21. package/src/core/python-kernel.lifecycle.test.ts +0 -249
  22. package/src/core/python-kernel.test.ts +0 -461
  23. package/src/core/python-modules.test.ts +0 -102
  24. package/src/core/python-prelude.test.ts +0 -140
  25. package/src/core/settings-manager-python.test.ts +0 -23
  26. package/src/core/streaming-output.test.ts +0 -26
  27. package/src/core/system-prompt.python.test.ts +0 -17
  28. package/src/core/tools/index.test.ts +0 -212
  29. package/src/core/tools/python-execution.test.ts +0 -68
  30. package/src/core/tools/python-fallback.test.ts +0 -72
  31. package/src/core/tools/python-renderer.test.ts +0 -36
  32. package/src/core/tools/python-tool-mode.test.ts +0 -43
  33. package/src/core/tools/python.test.ts +0 -121
  34. package/src/core/tools/schema-validation.test.ts +0 -530
  35. package/src/core/tools/web-scrapers/academic.test.ts +0 -239
  36. package/src/core/tools/web-scrapers/business.test.ts +0 -82
  37. package/src/core/tools/web-scrapers/dev-platforms.test.ts +0 -254
  38. package/src/core/tools/web-scrapers/documentation.test.ts +0 -85
  39. package/src/core/tools/web-scrapers/finance-media.test.ts +0 -144
  40. package/src/core/tools/web-scrapers/git-hosting.test.ts +0 -272
  41. package/src/core/tools/web-scrapers/media.test.ts +0 -138
  42. package/src/core/tools/web-scrapers/package-managers-2.test.ts +0 -199
  43. package/src/core/tools/web-scrapers/package-managers.test.ts +0 -171
  44. package/src/core/tools/web-scrapers/package-registries.test.ts +0 -259
  45. package/src/core/tools/web-scrapers/research.test.ts +0 -107
  46. package/src/core/tools/web-scrapers/security.test.ts +0 -103
  47. package/src/core/tools/web-scrapers/social-extended.test.ts +0 -192
  48. package/src/core/tools/web-scrapers/social.test.ts +0 -259
  49. package/src/core/tools/web-scrapers/stackexchange.test.ts +0 -120
  50. package/src/core/tools/web-scrapers/standards.test.ts +0 -122
  51. package/src/core/tools/web-scrapers/wikipedia.test.ts +0 -73
  52. package/src/core/tools/web-scrapers/youtube.test.ts +0 -198
  53. package/src/discovery/helpers.test.ts +0 -131
@@ -1,239 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { handleArxiv } from "./arxiv";
3
- import { handleIacr } from "./iacr";
4
- import { handlePubMed } from "./pubmed";
5
- import { handleSemanticScholar } from "./semantic-scholar";
6
-
7
- const SKIP = !process.env.WEB_FETCH_INTEGRATION;
8
-
9
- describe.skipIf(SKIP)("handleSemanticScholar", () => {
10
- it("returns null for non-S2 URLs", async () => {
11
- const result = await handleSemanticScholar("https://example.com", 10);
12
- expect(result).toBeNull();
13
- });
14
-
15
- it("fetches a known paper", async () => {
16
- // "Attention Is All You Need" paper
17
- const result = await handleSemanticScholar(
18
- "https://www.semanticscholar.org/paper/Attention-is-All-you-Need-Vaswani-Shazeer/204e3073870fae3d05bcbc2f6a8e263d9b72e776",
19
- 20,
20
- );
21
- expect(result).not.toBeNull();
22
- expect(result?.method).toBe("semantic-scholar");
23
- // API may be rate-limited or fail, verify handler structure
24
- if (
25
- !result?.content.includes("Too Many Requests") &&
26
- !result?.content.includes("Failed to fetch") &&
27
- !result?.content.includes("Failed to parse")
28
- ) {
29
- expect(result?.content).toContain("Attention");
30
- expect(result?.contentType).toBe("text/markdown");
31
- }
32
- expect(result?.truncated).toBe(false);
33
- });
34
-
35
- it("handles invalid paper ID format", async () => {
36
- const result = await handleSemanticScholar("https://www.semanticscholar.org/paper/invalid", 20);
37
- expect(result).not.toBeNull();
38
- expect(result?.method).toBe("semantic-scholar");
39
- expect(result?.content).toContain("Failed to extract paper ID");
40
- expect(result?.notes).toContain("Invalid URL format");
41
- });
42
-
43
- it("extracts paper ID from various URL formats", async () => {
44
- const paperId = "204e3073870fae3d05bcbc2f6a8e263d9b72e776";
45
- const urls = [
46
- `https://www.semanticscholar.org/paper/Attention-is-All-you-Need-Vaswani-Shazeer/${paperId}`,
47
- `https://www.semanticscholar.org/paper/${paperId}`,
48
- ];
49
-
50
- for (const url of urls) {
51
- const result = await handleSemanticScholar(url, 20);
52
- expect(result).not.toBeNull();
53
- expect(result?.method).toBe("semantic-scholar");
54
- // API may be rate-limited or fail
55
- if (
56
- !result?.content.includes("Too Many Requests") &&
57
- !result?.content.includes("Failed to fetch") &&
58
- !result?.content.includes("Failed to parse")
59
- ) {
60
- expect(result?.content).toContain("Attention");
61
- }
62
- }
63
- });
64
-
65
- it("includes metadata in formatted output", async () => {
66
- const result = await handleSemanticScholar(
67
- "https://www.semanticscholar.org/paper/Attention-is-All-you-Need-Vaswani-Shazeer/204e3073870fae3d05bcbc2f6a8e263d9b72e776",
68
- 20,
69
- );
70
- expect(result).not.toBeNull();
71
- // Only check metadata if API call succeeded (not rate-limited)
72
- if (!result?.content.includes("Too Many Requests") && !result?.content.includes("Failed to fetch")) {
73
- expect(result?.content).toMatch(/Year:/);
74
- expect(result?.content).toMatch(/Citations:/);
75
- expect(result?.content).toMatch(/Authors:/);
76
- expect(result?.content).toContain("Vaswani");
77
- }
78
- });
79
- });
80
-
81
- describe.skipIf(SKIP)("handlePubMed", () => {
82
- it("returns null for non-PubMed URLs", async () => {
83
- const result = await handlePubMed("https://example.com", 10);
84
- expect(result).toBeNull();
85
- });
86
-
87
- it("fetches a known article from pubmed.ncbi.nlm.nih.gov", async () => {
88
- // PMID 33782455 - COVID-19 vaccine paper
89
- const result = await handlePubMed("https://pubmed.ncbi.nlm.nih.gov/33782455/", 20);
90
- expect(result).not.toBeNull();
91
- expect(result?.method).toBe("pubmed");
92
- expect(result?.contentType).toBe("text/markdown");
93
- expect(result?.truncated).toBe(false);
94
- });
95
-
96
- it("fetches from ncbi.nlm.nih.gov/pubmed format", async () => {
97
- const result = await handlePubMed("https://ncbi.nlm.nih.gov/pubmed/33782455", 20);
98
- expect(result).not.toBeNull();
99
- expect(result?.method).toBe("pubmed");
100
- });
101
-
102
- it("includes PMID in output", async () => {
103
- const result = await handlePubMed("https://pubmed.ncbi.nlm.nih.gov/33782455/", 20);
104
- expect(result).not.toBeNull();
105
- expect(result?.content).toContain("PMID:");
106
- expect(result?.content).toContain("33782455");
107
- });
108
-
109
- it("includes abstract section", async () => {
110
- const result = await handlePubMed("https://pubmed.ncbi.nlm.nih.gov/33782455/", 20);
111
- expect(result).not.toBeNull();
112
- expect(result?.content).toContain("## Abstract");
113
- });
114
-
115
- it("includes metadata fields", async () => {
116
- const result = await handlePubMed("https://pubmed.ncbi.nlm.nih.gov/33782455/", 20);
117
- expect(result).not.toBeNull();
118
- expect(result?.content).toMatch(/Authors:/);
119
- expect(result?.content).toMatch(/Journal:/);
120
- });
121
-
122
- it("returns null for invalid PMID format", async () => {
123
- const result = await handlePubMed("https://pubmed.ncbi.nlm.nih.gov/invalid/", 20);
124
- expect(result).toBeNull();
125
- });
126
-
127
- it("handles non-existent PMID gracefully", async () => {
128
- const result = await handlePubMed("https://pubmed.ncbi.nlm.nih.gov/99999999999/", 20);
129
- // NCBI API returns a response even for non-existent PMIDs with minimal data
130
- expect(result).not.toBeNull();
131
- expect(result?.method).toBe("pubmed");
132
- });
133
- });
134
-
135
- describe.skipIf(SKIP)("handleArxiv", () => {
136
- it("returns null for non-arXiv URLs", async () => {
137
- const result = await handleArxiv("https://example.com", 10000);
138
- expect(result).toBeNull();
139
- });
140
-
141
- it("fetches a known paper", async () => {
142
- // "Attention Is All You Need" paper
143
- const result = await handleArxiv("https://arxiv.org/abs/1706.03762", 30000);
144
- expect(result).not.toBeNull();
145
- expect(result?.method).toBe("arxiv");
146
- expect(result?.contentType).toBe("text/markdown");
147
- // API may be rate-limited or fail
148
- if (!result?.content.includes("Too Many Requests") && !result?.content.includes("Failed to fetch")) {
149
- expect(result?.content).toContain("Attention");
150
- expect(result?.content).toContain("arXiv:");
151
- expect(result?.content).toContain("1706.03762");
152
- }
153
- expect(result?.truncated).toBe(false);
154
- });
155
-
156
- it("handles /pdf/ URLs", async () => {
157
- const result = await handleArxiv("https://arxiv.org/pdf/1706.03762", 30000);
158
- expect(result).not.toBeNull();
159
- expect(result?.method).toBe("arxiv");
160
- if (!result?.content.includes("Too Many Requests") && !result?.content.includes("Failed to fetch")) {
161
- expect(result?.content).toContain("Attention");
162
- expect(result?.notes?.some((n) => n.includes("PDF"))).toBe(true);
163
- }
164
- });
165
-
166
- it("handles arxiv.org/abs/ format", async () => {
167
- const result = await handleArxiv("https://arxiv.org/abs/1706.03762", 30000);
168
- expect(result).not.toBeNull();
169
- expect(result?.method).toBe("arxiv");
170
- if (!result?.content.includes("Too Many Requests") && !result?.content.includes("Failed to fetch")) {
171
- expect(result?.content).toContain("1706.03762");
172
- }
173
- });
174
-
175
- it("includes paper metadata", async () => {
176
- const result = await handleArxiv("https://arxiv.org/abs/1706.03762", 30000);
177
- expect(result).not.toBeNull();
178
- if (!result?.content.includes("Too Many Requests") && !result?.content.includes("Failed to fetch")) {
179
- expect(result?.content).toMatch(/Authors:/);
180
- expect(result?.content).toContain("Vaswani");
181
- expect(result?.content).toMatch(/Abstract/);
182
- expect(result?.content).toMatch(/Published:/);
183
- }
184
- });
185
-
186
- it("handles rate limiting gracefully", async () => {
187
- const result = await handleArxiv("https://arxiv.org/abs/1706.03762", 5000);
188
- expect(result).not.toBeNull();
189
- expect(result?.method).toBe("arxiv");
190
- // Should return something, even if rate limited
191
- expect(result?.content).toBeTruthy();
192
- });
193
- });
194
-
195
- describe.skipIf(SKIP)("handleIacr", () => {
196
- it("returns null for non-IACR URLs", async () => {
197
- const result = await handleIacr("https://example.com", 10000);
198
- expect(result).toBeNull();
199
- });
200
-
201
- it("fetches a known ePrint", async () => {
202
- // Using a well-known paper
203
- const result = await handleIacr("https://eprint.iacr.org/2023/123", 30000);
204
- expect(result).not.toBeNull();
205
- expect(result?.method).toBe("iacr");
206
- expect(result?.contentType).toBe("text/markdown");
207
- if (!result?.content.includes("Too Many Requests") && !result?.content.includes("Failed to fetch")) {
208
- expect(result?.content).toContain("ePrint:");
209
- expect(result?.content).toContain("2023/123");
210
- }
211
- expect(result?.truncated).toBe(false);
212
- });
213
-
214
- it("includes paper metadata", async () => {
215
- const result = await handleIacr("https://eprint.iacr.org/2023/123", 30000);
216
- expect(result).not.toBeNull();
217
- if (!result?.content.includes("Too Many Requests") && !result?.content.includes("Failed to fetch")) {
218
- expect(result?.content).toMatch(/Authors:/);
219
- expect(result?.content).toMatch(/Abstract/);
220
- }
221
- });
222
-
223
- it("handles rate limiting gracefully", async () => {
224
- const result = await handleIacr("https://eprint.iacr.org/2023/123", 5000);
225
- expect(result).not.toBeNull();
226
- expect(result?.method).toBe("iacr");
227
- // Should return something, even if rate limited
228
- expect(result?.content).toBeTruthy();
229
- });
230
-
231
- it("handles PDF URLs", async () => {
232
- const result = await handleIacr("https://eprint.iacr.org/2023/123.pdf", 30000);
233
- expect(result).not.toBeNull();
234
- expect(result?.method).toBe("iacr");
235
- if (!result?.content.includes("Too Many Requests") && !result?.content.includes("Failed to fetch")) {
236
- expect(result?.notes?.some((n) => n.includes("PDF"))).toBe(true);
237
- }
238
- });
239
- });
@@ -1,82 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { handleOpenCorporates } from "./opencorporates";
3
- import { handleSecEdgar } from "./sec-edgar";
4
-
5
- const SKIP = !process.env.WEB_FETCH_INTEGRATION;
6
-
7
- describe.skipIf(SKIP)("handleSecEdgar", () => {
8
- it("returns null for non-matching URLs", async () => {
9
- const result = await handleSecEdgar("https://example.com", 20);
10
- expect(result).toBeNull();
11
- });
12
-
13
- it("returns null for SEC URLs without valid CIK", async () => {
14
- const result = await handleSecEdgar("https://www.sec.gov/about.html", 20);
15
- expect(result).toBeNull();
16
- });
17
-
18
- it("fetches Apple Inc filings by CIK query param", async () => {
19
- const result = await handleSecEdgar(
20
- "https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK=0000320193",
21
- 20,
22
- );
23
- expect(result).not.toBeNull();
24
- expect(result?.method).toBe("sec-edgar");
25
- expect(result?.content).toContain("APPLE INC");
26
- expect(result?.content).toContain("0000320193");
27
- expect(result?.content).toContain("10-K"); // Apple files 10-K annually
28
- expect(result?.contentType).toBe("text/markdown");
29
- expect(result?.fetchedAt).toBeTruthy();
30
- expect(result?.truncated).toBeDefined();
31
- });
32
-
33
- it("fetches via data.sec.gov submissions URL", async () => {
34
- const result = await handleSecEdgar("https://data.sec.gov/submissions/CIK0000320193.json", 20);
35
- expect(result).not.toBeNull();
36
- expect(result?.method).toBe("sec-edgar");
37
- expect(result?.content).toContain("APPLE INC");
38
- });
39
-
40
- it("fetches via Archives path", async () => {
41
- // Any filing URL with CIK in Archives path
42
- const result = await handleSecEdgar(
43
- "https://www.sec.gov/Archives/edgar/data/320193/000032019324000123/aapl-20240928.htm",
44
- 20,
45
- );
46
- expect(result).not.toBeNull();
47
- expect(result?.method).toBe("sec-edgar");
48
- expect(result?.content).toContain("APPLE INC");
49
- });
50
- });
51
-
52
- describe.skipIf(SKIP)("handleOpenCorporates", () => {
53
- it("returns null for non-matching URLs", async () => {
54
- const result = await handleOpenCorporates("https://example.com", 20);
55
- expect(result).toBeNull();
56
- });
57
-
58
- it("returns null for OpenCorporates URLs without company path", async () => {
59
- const result = await handleOpenCorporates("https://opencorporates.com/about", 20);
60
- expect(result).toBeNull();
61
- });
62
-
63
- it("fetches Apple Inc Delaware registration", async () => {
64
- const result = await handleOpenCorporates("https://opencorporates.com/companies/us_de/2927442", 20);
65
- expect(result).not.toBeNull();
66
- expect(result?.method).toBe("opencorporates");
67
- expect(result?.content).toContain("APPLE INC");
68
- expect(result?.content).toContain("2927442");
69
- expect(result?.content).toContain("US_DE");
70
- expect(result?.contentType).toBe("text/markdown");
71
- expect(result?.fetchedAt).toBeTruthy();
72
- expect(result?.truncated).toBeDefined();
73
- });
74
-
75
- it("fetches Microsoft Corporation", async () => {
76
- // Microsoft is registered in Washington state
77
- const result = await handleOpenCorporates("https://opencorporates.com/companies/us_wa/600413485", 20);
78
- expect(result).not.toBeNull();
79
- expect(result?.method).toBe("opencorporates");
80
- expect(result?.content).toMatch(/microsoft/i);
81
- });
82
- });
@@ -1,254 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { handleDevTo } from "./devto";
3
- import { handleGitLab } from "./gitlab";
4
- import { handleHackerNews } from "./hackernews";
5
- import { handleLobsters } from "./lobsters";
6
-
7
- const SKIP = !process.env.WEB_FETCH_INTEGRATION;
8
-
9
- // =============================================================================
10
- // HackerNews Tests
11
- // =============================================================================
12
-
13
- describe.skipIf(SKIP)("handleHackerNews", () => {
14
- it("returns null for non-HN URLs", async () => {
15
- const result = await handleHackerNews("https://example.com", 10000);
16
- expect(result).toBeNull();
17
- });
18
-
19
- it("returns null for other domains", async () => {
20
- const result = await handleHackerNews("https://lobste.rs/", 10000);
21
- expect(result).toBeNull();
22
- });
23
-
24
- it("fetches front page", async () => {
25
- const result = await handleHackerNews("https://news.ycombinator.com/", 20000);
26
- expect(result).not.toBeNull();
27
- expect(result?.method).toBe("hackernews");
28
- expect(result?.contentType).toBe("text/markdown");
29
- expect(result?.content).toContain("Hacker News - Top Stories");
30
- expect(result?.content).toContain("points by");
31
- expect(result?.content).toContain("comments");
32
- });
33
-
34
- it("fetches individual story", async () => {
35
- const result = await handleHackerNews("https://news.ycombinator.com/item?id=1", 20000);
36
- expect(result).not.toBeNull();
37
- expect(result?.method).toBe("hackernews");
38
- expect(result?.content).toContain("Y Combinator");
39
- });
40
-
41
- it("fetches newest page", async () => {
42
- const result = await handleHackerNews("https://news.ycombinator.com/newest", 20000);
43
- expect(result).not.toBeNull();
44
- expect(result?.method).toBe("hackernews");
45
- expect(result?.content).toContain("Hacker News - New Stories");
46
- });
47
-
48
- it("fetches best page", async () => {
49
- const result = await handleHackerNews("https://news.ycombinator.com/best", 20000);
50
- expect(result).not.toBeNull();
51
- expect(result?.method).toBe("hackernews");
52
- expect(result?.content).toContain("Hacker News - Best Stories");
53
- });
54
-
55
- it("handles news alias", async () => {
56
- const result = await handleHackerNews("https://news.ycombinator.com/news", 20000);
57
- expect(result).not.toBeNull();
58
- expect(result?.method).toBe("hackernews");
59
- expect(result?.content).toContain("Hacker News - Top Stories");
60
- });
61
-
62
- it("returns null for unsupported paths", async () => {
63
- const result = await handleHackerNews("https://news.ycombinator.com/submit", 10000);
64
- expect(result).toBeNull();
65
- });
66
- });
67
-
68
- // =============================================================================
69
- // Lobsters Tests
70
- // =============================================================================
71
-
72
- describe.skipIf(SKIP)("handleLobsters", () => {
73
- it("returns null for non-Lobsters URLs", async () => {
74
- const result = await handleLobsters("https://example.com", 10000);
75
- expect(result).toBeNull();
76
- });
77
-
78
- it("returns null for other domains", async () => {
79
- const result = await handleLobsters("https://news.ycombinator.com/", 10000);
80
- expect(result).toBeNull();
81
- });
82
-
83
- it("returns null for front page due to handler bug", async () => {
84
- // Note: handler has bug with "https://lobste.rs.json" URL construction
85
- // Should use "/hottest.json" but currently constructs invalid URL
86
- const result = await handleLobsters("https://lobste.rs/", 20000);
87
- expect(result).toBeNull();
88
- });
89
-
90
- it("fetches newest page", async () => {
91
- const result = await handleLobsters("https://lobste.rs/newest", 20000);
92
- expect(result).not.toBeNull();
93
- expect(result?.method).toBe("lobsters");
94
- expect(result?.content).toContain("Lobste.rs Newest");
95
- });
96
-
97
- it("fetches tag page", async () => {
98
- const result = await handleLobsters("https://lobste.rs/t/programming", 20000);
99
- expect(result).not.toBeNull();
100
- expect(result?.method).toBe("lobsters");
101
- expect(result?.content).toContain("Lobste.rs Tag: programming");
102
- });
103
-
104
- it("fetches individual story", async () => {
105
- const result = await handleLobsters("https://lobste.rs/s/1uubbb", 20000);
106
- expect(result).not.toBeNull();
107
- expect(result?.method).toBe("lobsters");
108
- expect(result?.content).toContain("points");
109
- });
110
-
111
- it("handles tag with multiple path segments", async () => {
112
- const result = await handleLobsters("https://lobste.rs/t/rust", 20000);
113
- expect(result).not.toBeNull();
114
- expect(result?.method).toBe("lobsters");
115
- expect(result?.content).toContain("Lobste.rs Tag: rust");
116
- });
117
-
118
- it("returns null for invalid paths", async () => {
119
- const result = await handleLobsters("https://lobste.rs/invalid", 20000);
120
- expect(result).toBeNull();
121
- });
122
- });
123
-
124
- // =============================================================================
125
- // dev.to Tests
126
- // =============================================================================
127
-
128
- describe.skipIf(SKIP)("handleDevTo", () => {
129
- it("returns null for non-dev.to URLs", async () => {
130
- const result = await handleDevTo("https://example.com", 10000);
131
- expect(result).toBeNull();
132
- });
133
-
134
- it("returns null for other domains", async () => {
135
- const result = await handleDevTo("https://medium.com/@test", 10000);
136
- expect(result).toBeNull();
137
- });
138
-
139
- it("fetches tag page", async () => {
140
- const result = await handleDevTo("https://dev.to/t/javascript", 20000);
141
- expect(result).not.toBeNull();
142
- expect(result?.method).toBe("devto");
143
- expect(result?.contentType).toBe("text/markdown");
144
- expect(result?.content).toContain("dev.to/t/javascript");
145
- expect(result?.content).toContain("Recent Articles");
146
- });
147
-
148
- it("fetches another tag page", async () => {
149
- const result = await handleDevTo("https://dev.to/t/rust", 20000);
150
- expect(result).not.toBeNull();
151
- expect(result?.method).toBe("devto");
152
- expect(result?.content).toContain("dev.to/t/rust");
153
- });
154
-
155
- it("fetches user profile", async () => {
156
- const result = await handleDevTo("https://dev.to/ben", 20000);
157
- expect(result).not.toBeNull();
158
- expect(result?.method).toBe("devto");
159
- expect(result?.content).toContain("dev.to/ben");
160
- expect(result?.content).toContain("Recent Articles");
161
- });
162
-
163
- it("fetches individual article", async () => {
164
- const result = await handleDevTo("https://dev.to/ben/test", 20000);
165
- // May return null if article doesn't exist, but should not throw
166
- if (result !== null) {
167
- expect(result.method).toBe("devto");
168
- expect(result.contentType).toBe("text/markdown");
169
- }
170
- expect(result).toBeDefined();
171
- });
172
-
173
- it("handles tag with extra segments", async () => {
174
- const result = await handleDevTo("https://dev.to/t/webdev/top/week", 20000);
175
- expect(result).not.toBeNull();
176
- expect(result?.method).toBe("devto");
177
- expect(result?.content).toContain("dev.to/t/webdev");
178
- });
179
- });
180
-
181
- // =============================================================================
182
- // GitLab Tests
183
- // =============================================================================
184
-
185
- describe.skipIf(SKIP)("handleGitLab", () => {
186
- it("returns null for non-GitLab URLs", async () => {
187
- const result = await handleGitLab("https://example.com", 10000);
188
- expect(result).toBeNull();
189
- });
190
-
191
- it("returns null for github.com", async () => {
192
- const result = await handleGitLab("https://github.com/user/repo", 10000);
193
- expect(result).toBeNull();
194
- });
195
-
196
- it("fetches repository root", async () => {
197
- const result = await handleGitLab("https://gitlab.com/gitlab-org/gitlab", 20000);
198
- expect(result).not.toBeNull();
199
- expect(result?.method).toBe("gitlab-repo");
200
- expect(result?.contentType).toBe("text/markdown");
201
- expect(result?.content).toContain("Stars:");
202
- expect(result?.content).toContain("Forks:");
203
- });
204
-
205
- it("fetches another repository", async () => {
206
- const result = await handleGitLab("https://gitlab.com/gitlab-org/gitlab-runner", 20000);
207
- expect(result).not.toBeNull();
208
- expect(result?.method).toBe("gitlab-repo");
209
- });
210
-
211
- it("fetches file blob", async () => {
212
- const result = await handleGitLab("https://gitlab.com/gitlab-org/gitlab/-/blob/master/README.md", 20000);
213
- expect(result).not.toBeNull();
214
- expect(result?.method).toBe("gitlab-raw");
215
- expect(result?.contentType).toBe("text/plain");
216
- expect(result?.content.length).toBeGreaterThan(0);
217
- });
218
-
219
- it("fetches directory tree", async () => {
220
- const result = await handleGitLab("https://gitlab.com/gitlab-org/gitlab/-/tree/master", 20000);
221
- expect(result).not.toBeNull();
222
- expect(result?.method).toBe("gitlab-tree");
223
- expect(result?.contentType).toBe("text/markdown");
224
- expect(result?.content).toContain("Directory:");
225
- });
226
-
227
- it("fetches issue", async () => {
228
- const result = await handleGitLab("https://gitlab.com/gitlab-org/gitlab/-/issues/1", 20000);
229
- expect(result).not.toBeNull();
230
- expect(result?.method).toBe("gitlab-issue");
231
- expect(result?.contentType).toBe("text/markdown");
232
- expect(result?.content).toContain("Issue #1:");
233
- expect(result?.content).toContain("State:");
234
- });
235
-
236
- it("fetches merge request", async () => {
237
- const result = await handleGitLab("https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1", 20000);
238
- expect(result).not.toBeNull();
239
- expect(result?.method).toBe("gitlab-mr");
240
- expect(result?.contentType).toBe("text/markdown");
241
- expect(result?.content).toContain("MR !1:");
242
- expect(result?.content).toContain("State:");
243
- });
244
-
245
- it("returns null for invalid URL structure", async () => {
246
- const result = await handleGitLab("https://gitlab.com/", 10000);
247
- expect(result).toBeNull();
248
- });
249
-
250
- it("returns null for malformed paths", async () => {
251
- const result = await handleGitLab("https://gitlab.com/user", 10000);
252
- expect(result).toBeNull();
253
- });
254
- });
@@ -1,85 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { handleMDN } from "./mdn";
3
- import { handleReadTheDocs } from "./readthedocs";
4
-
5
- const SKIP = !process.env.WEB_FETCH_INTEGRATION;
6
-
7
- describe.skipIf(SKIP)("handleMDN", () => {
8
- it("returns null for non-MDN URLs", async () => {
9
- const result = await handleMDN("https://example.com", 20);
10
- expect(result).toBeNull();
11
- });
12
-
13
- it("returns null for non-docs MDN URLs", async () => {
14
- const result = await handleMDN("https://developer.mozilla.org/en-US/", 20);
15
- expect(result).toBeNull();
16
- });
17
-
18
- it("returns null for MDN blog URLs", async () => {
19
- const result = await handleMDN("https://developer.mozilla.org/en-US/blog/", 20);
20
- expect(result).toBeNull();
21
- });
22
-
23
- it("fetches Array.map documentation", async () => {
24
- const result = await handleMDN(
25
- "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map",
26
- 20,
27
- );
28
- expect(result).not.toBeNull();
29
- expect(result?.method).toBe("mdn");
30
- expect(result?.content).toContain("map");
31
- expect(result?.contentType).toBe("text/markdown");
32
- expect(result?.fetchedAt).toBeTruthy();
33
- });
34
-
35
- it("fetches Promise documentation", async () => {
36
- const result = await handleMDN(
37
- "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise",
38
- 20,
39
- );
40
- expect(result).not.toBeNull();
41
- expect(result?.method).toBe("mdn");
42
- expect(result?.content).toContain("Promise");
43
- expect(result?.truncated).toBeDefined();
44
- });
45
-
46
- it("fetches CSS documentation", async () => {
47
- const result = await handleMDN("https://developer.mozilla.org/en-US/docs/Web/CSS/display", 20);
48
- expect(result).not.toBeNull();
49
- expect(result?.method).toBe("mdn");
50
- expect(result?.content).toContain("display");
51
- });
52
- });
53
-
54
- describe.skipIf(SKIP)("handleReadTheDocs", () => {
55
- it("returns null for non-RTD URLs", async () => {
56
- const result = await handleReadTheDocs("https://example.com", 20);
57
- expect(result).toBeNull();
58
- });
59
-
60
- it("returns null for github.com URLs", async () => {
61
- const result = await handleReadTheDocs("https://github.com/user/repo", 20);
62
- expect(result).toBeNull();
63
- });
64
-
65
- it("fetches requests docs", async () => {
66
- const result = await handleReadTheDocs("https://requests.readthedocs.io/en/latest/", 20);
67
- expect(result).not.toBeNull();
68
- expect(result?.method).toBe("readthedocs");
69
- expect(result?.fetchedAt).toBeTruthy();
70
- expect(result?.truncated).toBeDefined();
71
- });
72
-
73
- it("returns null for non-readthedocs sites", async () => {
74
- // These sites use Sphinx/RTD theme but aren't hosted on readthedocs.io
75
- expect(await handleReadTheDocs("https://www.sphinx-doc.org/en/master/", 20)).toBeNull();
76
- expect(await handleReadTheDocs("https://docs.pytest.org/en/stable/", 20)).toBeNull();
77
- expect(await handleReadTheDocs("https://pip.pypa.io/en/stable/", 20)).toBeNull();
78
- });
79
-
80
- it("handles readthedocs.io subdomain", async () => {
81
- const result = await handleReadTheDocs("https://flask.palletsprojects.readthedocs.io/en/latest/", 20);
82
- expect(result).not.toBeNull();
83
- expect(result?.method).toBe("readthedocs");
84
- });
85
- });