| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- import { assertEquals } from "@std/assert";
- import { find, insert } from "utils/db.ts";
- import { getCryptoString } from "utils/server.ts";
- import { getSetCookies } from "@std/http";
- let testCounter = 0;
- function getTestDbPath() {
- testCounter++;
- return `data/test_api_share_${testCounter}_${Date.now()}.db`;
- }
- function cleanup(dbPath: string) {
- try {
- Deno.removeSync(dbPath);
- } catch {
- // File may not exist
- }
- }
- function makeCtx(method: string, body?: object, cookie?: string) {
- const headers: Record<string, string> = {
- "Content-Type": "application/json",
- };
- if (cookie) headers["Cookie"] = cookie;
- const req = new Request("http://localhost", {
- method,
- headers,
- body: method === "GET" ? undefined : JSON.stringify(body || {}),
- });
- return { req };
- }
- async function seedUserAndToken(email: string, password: string) {
- const hashedPw = await getCryptoString(password, "MD5");
- const user = insert("User", {
- name: email.split("@")[0],
- email,
- password: hashedPw,
- });
- const userId = user[0]["id"] as string | number;
- const token = await getCryptoString(
- "share-token-" + userId + Date.now(),
- "MD5",
- );
- insert("Token", { token, user_id: userId });
- return { userId, token };
- }
- Deno.test("API share - share a post", async () => {
- const dbPath = getTestDbPath();
- Deno.env.set("POSTDOWN_DB_PATH", dbPath);
- try {
- const { userId, token } = await seedUserAndToken(
- "shareshare@example.com",
- "password",
- );
- insert("Post", {
- id: "share-post-1",
- title: "Share Me",
- content: "Content",
- user_id: userId,
- shared: 0,
- });
- const { handler } = await import("../../routes/api/share/index.tsx");
- const ctx = makeCtx("POST", {
- id: "share-post-1",
- shared: true,
- }, `pd-user-token=${token}`);
- const res = await handler.POST!(ctx as any);
- const body = await res.json();
- assertEquals(body.success, true);
- const post = find("Post", { id: "share-post-1" }, ["shared"]);
- assertEquals(post[0]["shared"], 1);
- } finally {
- Deno.env.delete("POSTDOWN_DB_PATH");
- cleanup(dbPath);
- }
- });
- Deno.test("API share - unshare a post", async () => {
- const dbPath = getTestDbPath();
- Deno.env.set("POSTDOWN_DB_PATH", dbPath);
- try {
- const { userId, token } = await seedUserAndToken(
- "shareunshare@example.com",
- "password",
- );
- insert("Post", {
- id: "unshare-post-1",
- title: "Unshare Me",
- content: "Content",
- user_id: userId,
- shared: 1,
- });
- const { handler } = await import("../../routes/api/share/index.tsx");
- const ctx = makeCtx("POST", {
- id: "unshare-post-1",
- shared: false,
- }, `pd-user-token=${token}`);
- const res = await handler.POST!(ctx as any);
- const body = await res.json();
- assertEquals(body.success, true);
- const post = find("Post", { id: "unshare-post-1" }, ["shared"]);
- assertEquals(post[0]["shared"], 0);
- } finally {
- Deno.env.delete("POSTDOWN_DB_PATH");
- cleanup(dbPath);
- }
- });
- Deno.test("API share - without token returns error", async () => {
- const dbPath = getTestDbPath();
- Deno.env.set("POSTDOWN_DB_PATH", dbPath);
- try {
- const { handler } = await import("../../routes/api/share/index.tsx");
- const ctx = makeCtx("POST", {
- id: "some-post",
- shared: true,
- });
- const res = await handler.POST!(ctx as any);
- const body = await res.json();
- assertEquals(body.success, false);
- } finally {
- Deno.env.delete("POSTDOWN_DB_PATH");
- cleanup(dbPath);
- }
- });
- Deno.test("API share - share another user's post returns error", async () => {
- const dbPath = getTestDbPath();
- Deno.env.set("POSTDOWN_DB_PATH", dbPath);
- try {
- const { userId: otherUserId } = await seedUserAndToken(
- "shareother@example.com",
- "password",
- );
- insert("Post", {
- id: "other-post-1",
- title: "Other's Post",
- content: "Content",
- user_id: otherUserId,
- shared: 0,
- });
- const { token: myToken } = await seedUserAndToken(
- "shareme@example.com",
- "password2",
- );
- const { handler } = await import("../../routes/api/share/index.tsx");
- const ctx = makeCtx("POST", {
- id: "other-post-1",
- shared: true,
- }, `pd-user-token=${myToken}`);
- const res = await handler.POST!(ctx as any);
- const body = await res.json();
- assertEquals(body.success, false);
- } finally {
- Deno.env.delete("POSTDOWN_DB_PATH");
- cleanup(dbPath);
- }
- });
- Deno.test("API share - share with password stores hashed password", async () => {
- const dbPath = getTestDbPath();
- Deno.env.set("POSTDOWN_DB_PATH", dbPath);
- try {
- const { userId, token } = await seedUserAndToken(
- "sharepw@example.com",
- "password",
- );
- insert("Post", {
- id: "pw-post-1",
- title: "Password Post",
- content: "Secret content",
- user_id: userId,
- shared: 0,
- });
- const { handler } = await import("../../routes/api/share/index.tsx");
- const ctx = makeCtx("POST", {
- id: "pw-post-1",
- shared: true,
- password: "mypassword",
- }, `pd-user-token=${token}`);
- const res = await handler.POST!(ctx as any);
- const body = await res.json();
- assertEquals(body.success, true);
- const post = find("Post", { id: "pw-post-1" }, [
- "shared",
- "share_password",
- ]);
- assertEquals(post[0]["shared"], 1);
- const expectedHash = await getCryptoString("mypassword", "MD5");
- assertEquals(post[0]["share_password"], expectedHash);
- } finally {
- Deno.env.delete("POSTDOWN_DB_PATH");
- cleanup(dbPath);
- }
- });
- Deno.test("API share - unshare clears password", async () => {
- const dbPath = getTestDbPath();
- Deno.env.set("POSTDOWN_DB_PATH", dbPath);
- try {
- const { userId, token } = await seedUserAndToken(
- "unshpw@example.com",
- "password",
- );
- const hashedPw = await getCryptoString("oldpw", "MD5");
- insert("Post", {
- id: "pw-post-2",
- title: "PW Post",
- content: "Content",
- user_id: userId,
- shared: 1,
- share_password: hashedPw,
- });
- const { handler } = await import("../../routes/api/share/index.tsx");
- const ctx = makeCtx("POST", {
- id: "pw-post-2",
- shared: false,
- }, `pd-user-token=${token}`);
- const res = await handler.POST!(ctx as any);
- const body = await res.json();
- assertEquals(body.success, true);
- const post = find("Post", { id: "pw-post-2" }, [
- "shared",
- "share_password",
- ]);
- assertEquals(post[0]["shared"], 0);
- assertEquals(post[0]["share_password"], "");
- } finally {
- Deno.env.delete("POSTDOWN_DB_PATH");
- cleanup(dbPath);
- }
- });
- Deno.test("API share-verify - correct password returns success and sets cookie", async () => {
- const dbPath = getTestDbPath();
- Deno.env.set("POSTDOWN_DB_PATH", dbPath);
- try {
- const hashedPw = await getCryptoString("secret123", "MD5");
- const { userId } = await seedUserAndToken(
- "verifypw@example.com",
- "password",
- );
- insert("Post", {
- id: "verify-post-1",
- title: "Protected",
- content: "Secret",
- user_id: userId,
- shared: 1,
- share_password: hashedPw,
- });
- const { handler } = await import("../../routes/api/share/verify.tsx");
- const ctx = makeCtx("POST", {
- id: "verify-post-1",
- password: "secret123",
- });
- const res = await handler.POST!(ctx as any);
- const body = await res.json();
- assertEquals(body.success, true);
- const setCookies = getSetCookies(res.headers);
- const shareCookie = setCookies.find((c) =>
- c.name === "pd-share-verify-post-1"
- );
- const expectedToken = await getCryptoString(
- "verify-post-1" + hashedPw,
- "MD5",
- );
- assertEquals(shareCookie?.value, expectedToken);
- } finally {
- Deno.env.delete("POSTDOWN_DB_PATH");
- cleanup(dbPath);
- }
- });
- Deno.test("API share-verify - wrong password returns error", async () => {
- const dbPath = getTestDbPath();
- Deno.env.set("POSTDOWN_DB_PATH", dbPath);
- try {
- const hashedPw = await getCryptoString("secret123", "MD5");
- const { userId } = await seedUserAndToken(
- "verifywrong@example.com",
- "password",
- );
- insert("Post", {
- id: "verify-post-2",
- title: "Protected",
- content: "Secret",
- user_id: userId,
- shared: 1,
- share_password: hashedPw,
- });
- const { handler } = await import("../../routes/api/share/verify.tsx");
- const ctx = makeCtx("POST", {
- id: "verify-post-2",
- password: "wrongpassword",
- });
- const res = await handler.POST!(ctx as any);
- const body = await res.json();
- assertEquals(body.success, false);
- } finally {
- Deno.env.delete("POSTDOWN_DB_PATH");
- cleanup(dbPath);
- }
- });
|