@jant/core 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/routes/api/posts.js +1 -1
- package/package.json +10 -4
- package/src/__tests__/helpers/app.ts +97 -0
- package/src/__tests__/helpers/db.ts +85 -0
- package/src/lib/__tests__/constants.test.ts +44 -0
- package/src/lib/__tests__/markdown.test.ts +133 -0
- package/src/lib/__tests__/schemas.test.ts +220 -0
- package/src/lib/__tests__/sqid.test.ts +65 -0
- package/src/lib/__tests__/sse.test.ts +86 -0
- package/src/lib/__tests__/time.test.ts +112 -0
- package/src/lib/__tests__/url.test.ts +138 -0
- package/src/middleware/__tests__/auth.test.ts +139 -0
- package/src/routes/api/__tests__/posts.test.ts +306 -0
- package/src/routes/api/__tests__/search.test.ts +77 -0
- package/src/routes/api/posts.ts +3 -1
- package/src/services/__tests__/collection.test.ts +226 -0
- package/src/services/__tests__/media.test.ts +134 -0
- package/src/services/__tests__/post.test.ts +636 -0
- package/src/services/__tests__/redirect.test.ts +110 -0
- package/src/services/__tests__/search.test.ts +143 -0
- package/src/services/__tests__/settings.test.ts +110 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { dsRedirect, dsToast, dsSignals } from "../sse.js";
|
|
3
|
+
|
|
4
|
+
describe("dsRedirect", () => {
|
|
5
|
+
it("returns a Response with text/html content-type", () => {
|
|
6
|
+
const res = dsRedirect("/dash");
|
|
7
|
+
expect(res.headers.get("Content-Type")).toBe("text/html");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("includes Datastar headers for append mode", () => {
|
|
11
|
+
const res = dsRedirect("/dash");
|
|
12
|
+
expect(res.headers.get("Datastar-Mode")).toBe("append");
|
|
13
|
+
expect(res.headers.get("Datastar-Selector")).toBe("body");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("body contains redirect script with correct URL", async () => {
|
|
17
|
+
const res = dsRedirect("/dash/posts");
|
|
18
|
+
const body = await res.text();
|
|
19
|
+
expect(body).toContain("window.location.href='/dash/posts'");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("escapes single quotes in URL", async () => {
|
|
23
|
+
const res = dsRedirect("/path/with'quote");
|
|
24
|
+
const body = await res.text();
|
|
25
|
+
expect(body).toContain("\\'");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("merges additional headers", () => {
|
|
29
|
+
const res = dsRedirect("/dash", {
|
|
30
|
+
headers: { "Set-Cookie": "session=abc" },
|
|
31
|
+
});
|
|
32
|
+
expect(res.headers.get("Set-Cookie")).toBe("session=abc");
|
|
33
|
+
expect(res.headers.get("Content-Type")).toBe("text/html");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("dsToast", () => {
|
|
38
|
+
it("returns text/html content-type", () => {
|
|
39
|
+
const res = dsToast("Saved!");
|
|
40
|
+
expect(res.headers.get("Content-Type")).toBe("text/html");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("targets #toast-container", () => {
|
|
44
|
+
const res = dsToast("Saved!");
|
|
45
|
+
expect(res.headers.get("Datastar-Selector")).toBe("#toast-container");
|
|
46
|
+
expect(res.headers.get("Datastar-Mode")).toBe("append");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("defaults to success type", async () => {
|
|
50
|
+
const res = dsToast("Saved!");
|
|
51
|
+
const body = await res.text();
|
|
52
|
+
expect(body).toContain("toast-success");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("supports error type", async () => {
|
|
56
|
+
const res = dsToast("Failed!", "error");
|
|
57
|
+
const body = await res.text();
|
|
58
|
+
expect(body).toContain("toast-error");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("escapes HTML in message", async () => {
|
|
62
|
+
const res = dsToast("<script>alert('xss')</script>");
|
|
63
|
+
const body = await res.text();
|
|
64
|
+
expect(body).not.toContain("<script>alert");
|
|
65
|
+
expect(body).toContain("<script>");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("dsSignals", () => {
|
|
70
|
+
it("returns application/json content-type", () => {
|
|
71
|
+
const res = dsSignals({ count: 1 });
|
|
72
|
+
expect(res.headers.get("Content-Type")).toBe("application/json");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("body contains JSON-serialized signals", async () => {
|
|
76
|
+
const res = dsSignals({ _error: "File too large", count: 42 });
|
|
77
|
+
const body = await res.json();
|
|
78
|
+
expect(body).toEqual({ _error: "File too large", count: 42 });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("handles empty signals", async () => {
|
|
82
|
+
const res = dsSignals({});
|
|
83
|
+
const body = await res.json();
|
|
84
|
+
expect(body).toEqual({});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
now,
|
|
4
|
+
isWithinMonth,
|
|
5
|
+
toISOString,
|
|
6
|
+
formatDate,
|
|
7
|
+
formatYearMonth,
|
|
8
|
+
} from "../time.js";
|
|
9
|
+
|
|
10
|
+
describe("now", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vi.restoreAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("returns current time in seconds", () => {
|
|
16
|
+
const before = Math.floor(Date.now() / 1000);
|
|
17
|
+
const result = now();
|
|
18
|
+
const after = Math.floor(Date.now() / 1000);
|
|
19
|
+
expect(result).toBeGreaterThanOrEqual(before);
|
|
20
|
+
expect(result).toBeLessThanOrEqual(after);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("returns an integer (not milliseconds)", () => {
|
|
24
|
+
const result = now();
|
|
25
|
+
expect(Number.isInteger(result)).toBe(true);
|
|
26
|
+
// Should be in seconds, not milliseconds (less than 10 billion)
|
|
27
|
+
expect(result).toBeLessThan(10_000_000_000);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("isWithinMonth", () => {
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
vi.restoreAllMocks();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("returns true for a timestamp within the last 30 days", () => {
|
|
37
|
+
const recent = now() - 60 * 60; // 1 hour ago
|
|
38
|
+
expect(isWithinMonth(recent)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("returns true for current timestamp", () => {
|
|
42
|
+
expect(isWithinMonth(now())).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("returns false for a timestamp older than 30 days", () => {
|
|
46
|
+
const old = now() - 31 * 24 * 60 * 60; // 31 days ago
|
|
47
|
+
expect(isWithinMonth(old)).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("returns false for timestamp exactly at 30-day boundary", () => {
|
|
51
|
+
// 30 days = 2592000 seconds
|
|
52
|
+
const boundary = now() - 30 * 24 * 60 * 60;
|
|
53
|
+
expect(isWithinMonth(boundary)).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("returns true for timestamp just under 30 days", () => {
|
|
57
|
+
const justUnder = now() - (30 * 24 * 60 * 60 - 1);
|
|
58
|
+
expect(isWithinMonth(justUnder)).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("toISOString", () => {
|
|
63
|
+
it("converts Unix timestamp to ISO string", () => {
|
|
64
|
+
// Feb 1, 2024 00:00:00 UTC
|
|
65
|
+
expect(toISOString(1706745600)).toBe("2024-02-01T00:00:00.000Z");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("converts epoch 0", () => {
|
|
69
|
+
expect(toISOString(0)).toBe("1970-01-01T00:00:00.000Z");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("handles timestamps with time components", () => {
|
|
73
|
+
// 2024-01-15T12:30:00Z = 1705321800
|
|
74
|
+
expect(toISOString(1705321800)).toBe("2024-01-15T12:30:00.000Z");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("formatDate", () => {
|
|
79
|
+
it("formats as MMM DD, YYYY", () => {
|
|
80
|
+
expect(formatDate(1706745600)).toBe("Feb 1, 2024");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("formats epoch start", () => {
|
|
84
|
+
expect(formatDate(0)).toBe("Jan 1, 1970");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("uses UTC timezone consistently", () => {
|
|
88
|
+
// Dec 31, 2023 23:59:59 UTC
|
|
89
|
+
const timestamp = 1704067199;
|
|
90
|
+
expect(formatDate(timestamp)).toBe("Dec 31, 2023");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("formatYearMonth", () => {
|
|
95
|
+
it("formats as YYYY-MM", () => {
|
|
96
|
+
expect(formatYearMonth(1706745600)).toBe("2024-02");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("zero-pads single-digit months", () => {
|
|
100
|
+
// Jan 15, 2024
|
|
101
|
+
expect(formatYearMonth(1705276800)).toBe("2024-01");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("handles December correctly", () => {
|
|
105
|
+
// Dec 15, 2023
|
|
106
|
+
expect(formatYearMonth(1702598400)).toBe("2023-12");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("formats epoch start", () => {
|
|
110
|
+
expect(formatYearMonth(0)).toBe("1970-01");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { extractDomain, normalizePath, isFullUrl, slugify } from "../url.js";
|
|
3
|
+
|
|
4
|
+
describe("extractDomain", () => {
|
|
5
|
+
it("extracts hostname from HTTPS URL", () => {
|
|
6
|
+
expect(extractDomain("https://example.com/path")).toBe("example.com");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("extracts hostname from HTTP URL", () => {
|
|
10
|
+
expect(extractDomain("http://example.com")).toBe("example.com");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("includes www subdomain", () => {
|
|
14
|
+
expect(extractDomain("https://www.example.com/path")).toBe(
|
|
15
|
+
"www.example.com",
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("handles URLs with ports", () => {
|
|
20
|
+
expect(extractDomain("http://localhost:3000/api")).toBe("localhost");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("handles URLs with query params and hash", () => {
|
|
24
|
+
expect(extractDomain("https://example.com/path?q=1#section")).toBe(
|
|
25
|
+
"example.com",
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("returns null for invalid URLs", () => {
|
|
30
|
+
expect(extractDomain("not-a-url")).toBe(null);
|
|
31
|
+
expect(extractDomain("")).toBe(null);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("handles complex subdomains", () => {
|
|
35
|
+
expect(extractDomain("https://blog.sub.example.com")).toBe(
|
|
36
|
+
"blog.sub.example.com",
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("normalizePath", () => {
|
|
42
|
+
it("converts to lowercase", () => {
|
|
43
|
+
expect(normalizePath("About")).toBe("about");
|
|
44
|
+
expect(normalizePath("HELLO")).toBe("hello");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("removes leading and trailing slashes", () => {
|
|
48
|
+
expect(normalizePath("/about/")).toBe("about");
|
|
49
|
+
expect(normalizePath("///about///")).toBe("about");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("collapses multiple slashes", () => {
|
|
53
|
+
expect(normalizePath("about//contact")).toBe("about/contact");
|
|
54
|
+
expect(normalizePath("a///b////c")).toBe("a/b/c");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("trims whitespace", () => {
|
|
58
|
+
expect(normalizePath(" about ")).toBe("about");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("handles combined transformations", () => {
|
|
62
|
+
expect(normalizePath(" /About/Contact// ")).toBe("about/contact");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("returns empty string for root path", () => {
|
|
66
|
+
expect(normalizePath("/")).toBe("");
|
|
67
|
+
expect(normalizePath("///")).toBe("");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("handles empty input", () => {
|
|
71
|
+
expect(normalizePath("")).toBe("");
|
|
72
|
+
expect(normalizePath(" ")).toBe("");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("isFullUrl", () => {
|
|
77
|
+
it("returns true for https URLs", () => {
|
|
78
|
+
expect(isFullUrl("https://example.com")).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("returns true for http URLs", () => {
|
|
82
|
+
expect(isFullUrl("http://example.com")).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("returns false for relative paths", () => {
|
|
86
|
+
expect(isFullUrl("/about")).toBe(false);
|
|
87
|
+
expect(isFullUrl("about")).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("returns false for domain-only strings", () => {
|
|
91
|
+
expect(isFullUrl("example.com")).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns false for empty string", () => {
|
|
95
|
+
expect(isFullUrl("")).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("returns false for other protocols", () => {
|
|
99
|
+
expect(isFullUrl("ftp://example.com")).toBe(false);
|
|
100
|
+
expect(isFullUrl("mailto:test@test.com")).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("slugify", () => {
|
|
105
|
+
it("converts text to lowercase hyphenated slug", () => {
|
|
106
|
+
expect(slugify("Hello World")).toBe("hello-world");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("removes special characters", () => {
|
|
110
|
+
expect(slugify("Hello World! This is a Test.")).toBe(
|
|
111
|
+
"hello-world-this-is-a-test",
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("collapses multiple spaces", () => {
|
|
116
|
+
expect(slugify("Multiple Spaces")).toBe("multiple-spaces");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("trims leading/trailing whitespace and hyphens", () => {
|
|
120
|
+
expect(slugify(" Multiple Spaces ")).toBe("multiple-spaces");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("replaces underscores with hyphens", () => {
|
|
124
|
+
expect(slugify("hello_world")).toBe("hello-world");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("handles already-slugified text", () => {
|
|
128
|
+
expect(slugify("already-a-slug")).toBe("already-a-slug");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("handles empty string", () => {
|
|
132
|
+
expect(slugify("")).toBe("");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("removes non-word characters", () => {
|
|
136
|
+
expect(slugify("café & résumé")).toBe("caf-rsum");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { requireAuth, requireAuthApi } from "../auth.js";
|
|
4
|
+
import type { Bindings } from "../../types.js";
|
|
5
|
+
import type { AppVariables } from "../../app.js";
|
|
6
|
+
|
|
7
|
+
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
8
|
+
|
|
9
|
+
function createMockAuth(authenticated: boolean) {
|
|
10
|
+
return {
|
|
11
|
+
api: {
|
|
12
|
+
getSession: async () =>
|
|
13
|
+
authenticated
|
|
14
|
+
? {
|
|
15
|
+
user: { id: "user-1", email: "test@test.com", name: "Test" },
|
|
16
|
+
session: { id: "session-1" },
|
|
17
|
+
}
|
|
18
|
+
: null,
|
|
19
|
+
},
|
|
20
|
+
} as AppVariables["auth"];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("requireAuth", () => {
|
|
24
|
+
it("allows authenticated requests", async () => {
|
|
25
|
+
const app = new Hono<Env>();
|
|
26
|
+
app.use("*", async (c, next) => {
|
|
27
|
+
c.set("auth", createMockAuth(true));
|
|
28
|
+
await next();
|
|
29
|
+
});
|
|
30
|
+
app.get("/dash", requireAuth(), (c) => c.text("Dashboard"));
|
|
31
|
+
|
|
32
|
+
const res = await app.request("/dash");
|
|
33
|
+
expect(res.status).toBe(200);
|
|
34
|
+
expect(await res.text()).toBe("Dashboard");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("redirects unauthenticated requests to /signin", async () => {
|
|
38
|
+
const app = new Hono<Env>();
|
|
39
|
+
app.use("*", async (c, next) => {
|
|
40
|
+
c.set("auth", createMockAuth(false));
|
|
41
|
+
await next();
|
|
42
|
+
});
|
|
43
|
+
app.get("/dash", requireAuth(), (c) => c.text("Dashboard"));
|
|
44
|
+
|
|
45
|
+
const res = await app.request("/dash", { redirect: "manual" });
|
|
46
|
+
expect(res.status).toBe(302);
|
|
47
|
+
expect(res.headers.get("Location")).toBe("/signin");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("redirects to custom path", async () => {
|
|
51
|
+
const app = new Hono<Env>();
|
|
52
|
+
app.use("*", async (c, next) => {
|
|
53
|
+
c.set("auth", createMockAuth(false));
|
|
54
|
+
await next();
|
|
55
|
+
});
|
|
56
|
+
app.get("/dash", requireAuth("/login"), (c) => c.text("Dashboard"));
|
|
57
|
+
|
|
58
|
+
const res = await app.request("/dash", { redirect: "manual" });
|
|
59
|
+
expect(res.status).toBe(302);
|
|
60
|
+
expect(res.headers.get("Location")).toBe("/login");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("redirects when auth is not configured", async () => {
|
|
64
|
+
const app = new Hono<Env>();
|
|
65
|
+
app.use("*", async (c, next) => {
|
|
66
|
+
// auth not set (undefined)
|
|
67
|
+
await next();
|
|
68
|
+
});
|
|
69
|
+
app.get("/dash", requireAuth(), (c) => c.text("Dashboard"));
|
|
70
|
+
|
|
71
|
+
const res = await app.request("/dash", { redirect: "manual" });
|
|
72
|
+
expect(res.status).toBe(302);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("requireAuthApi", () => {
|
|
77
|
+
it("allows authenticated requests", async () => {
|
|
78
|
+
const app = new Hono<Env>();
|
|
79
|
+
app.use("*", async (c, next) => {
|
|
80
|
+
c.set("auth", createMockAuth(true));
|
|
81
|
+
await next();
|
|
82
|
+
});
|
|
83
|
+
app.get("/api/data", requireAuthApi(), (c) => c.json({ data: "secret" }));
|
|
84
|
+
|
|
85
|
+
const res = await app.request("/api/data");
|
|
86
|
+
expect(res.status).toBe(200);
|
|
87
|
+
|
|
88
|
+
const body = await res.json();
|
|
89
|
+
expect(body.data).toBe("secret");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("returns 401 for unauthenticated requests", async () => {
|
|
93
|
+
const app = new Hono<Env>();
|
|
94
|
+
app.use("*", async (c, next) => {
|
|
95
|
+
c.set("auth", createMockAuth(false));
|
|
96
|
+
await next();
|
|
97
|
+
});
|
|
98
|
+
app.get("/api/data", requireAuthApi(), (c) => c.json({ data: "secret" }));
|
|
99
|
+
|
|
100
|
+
const res = await app.request("/api/data");
|
|
101
|
+
expect(res.status).toBe(401);
|
|
102
|
+
|
|
103
|
+
const body = await res.json();
|
|
104
|
+
expect(body.error).toBe("Unauthorized");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("returns 500 when auth is not configured", async () => {
|
|
108
|
+
const app = new Hono<Env>();
|
|
109
|
+
app.use("*", async (c, next) => {
|
|
110
|
+
// auth not set (undefined)
|
|
111
|
+
await next();
|
|
112
|
+
});
|
|
113
|
+
app.get("/api/data", requireAuthApi(), (c) => c.json({ data: "secret" }));
|
|
114
|
+
|
|
115
|
+
const res = await app.request("/api/data");
|
|
116
|
+
expect(res.status).toBe(500);
|
|
117
|
+
|
|
118
|
+
const body = await res.json();
|
|
119
|
+
expect(body.error).toBe("Authentication not configured");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("returns 401 when getSession throws", async () => {
|
|
123
|
+
const app = new Hono<Env>();
|
|
124
|
+
app.use("*", async (c, next) => {
|
|
125
|
+
c.set("auth", {
|
|
126
|
+
api: {
|
|
127
|
+
getSession: async () => {
|
|
128
|
+
throw new Error("Session error");
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
} as AppVariables["auth"]);
|
|
132
|
+
await next();
|
|
133
|
+
});
|
|
134
|
+
app.get("/api/data", requireAuthApi(), (c) => c.json({ data: "secret" }));
|
|
135
|
+
|
|
136
|
+
const res = await app.request("/api/data");
|
|
137
|
+
expect(res.status).toBe(401);
|
|
138
|
+
});
|
|
139
|
+
});
|