|
|
@@ -0,0 +1,272 @@
|
|
|
+import { assertEquals } from "@std/assert";
|
|
|
+import { find, insert } from "utils/db.ts";
|
|
|
+import { getCryptoString } from "utils/server.ts";
|
|
|
+
|
|
|
+let testCounter = 0;
|
|
|
+
|
|
|
+function getTestDbPath() {
|
|
|
+ testCounter++;
|
|
|
+ return `data/test_api_user_${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 seedUser(email: string, password: string) {
|
|
|
+ const hashedPw = await getCryptoString(password, "MD5");
|
|
|
+ const user = insert("User", {
|
|
|
+ name: email.split("@")[0],
|
|
|
+ email,
|
|
|
+ password: hashedPw,
|
|
|
+ });
|
|
|
+ return user;
|
|
|
+}
|
|
|
+
|
|
|
+async function seedToken(userId: string | number) {
|
|
|
+ const token = await getCryptoString("test-token-" + userId + Date.now(), "MD5");
|
|
|
+ insert("Token", { token, user_id: userId });
|
|
|
+ return token;
|
|
|
+}
|
|
|
+
|
|
|
+Deno.test("API user/register - successful registration", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ const { handler } = await import("../../routes/api/user/register.tsx");
|
|
|
+ const ctx = makeCtx("POST", {
|
|
|
+ email: "newuser@example.com",
|
|
|
+ password: "password123",
|
|
|
+ });
|
|
|
+ const res = await handler.POST!(ctx as any);
|
|
|
+ const body = await res.json();
|
|
|
+ assertEquals(body.success, true);
|
|
|
+ assertEquals(body.data, true);
|
|
|
+
|
|
|
+ const users = find("User", { email: "newuser@example.com" }, ["id"]);
|
|
|
+ assertEquals(users.length, 1);
|
|
|
+ } finally {
|
|
|
+ Deno.env.delete("POSTDOWN_DB_PATH");
|
|
|
+ cleanup(dbPath);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+Deno.test("API user/register - missing email returns error", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ const { handler } = await import("../../routes/api/user/register.tsx");
|
|
|
+ const ctx = makeCtx("POST", { password: "password123" });
|
|
|
+ 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 user/register - missing password returns error", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ const { handler } = await import("../../routes/api/user/register.tsx");
|
|
|
+ const ctx = makeCtx("POST", { email: "test@example.com" });
|
|
|
+ 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 user/login - successful login", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ await seedUser("login@example.com", "mypassword");
|
|
|
+
|
|
|
+ const { handler } = await import("../../routes/api/user/login.tsx");
|
|
|
+ const ctx = makeCtx("POST", {
|
|
|
+ email: "login@example.com",
|
|
|
+ password: "mypassword",
|
|
|
+ });
|
|
|
+ const res = await handler.POST!(ctx as any);
|
|
|
+ const body = await res.json();
|
|
|
+ assertEquals(body.success, true);
|
|
|
+ assertEquals(body.data, true);
|
|
|
+
|
|
|
+ const setCookie = res.headers.get("set-cookie");
|
|
|
+ assertEquals(setCookie !== null, true);
|
|
|
+ assertEquals(setCookie!.includes("pd-user-token="), true);
|
|
|
+ } finally {
|
|
|
+ Deno.env.delete("POSTDOWN_DB_PATH");
|
|
|
+ cleanup(dbPath);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+Deno.test("API user/login - wrong email returns error", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ const { handler } = await import("../../routes/api/user/login.tsx");
|
|
|
+ const ctx = makeCtx("POST", {
|
|
|
+ email: "nonexistent@example.com",
|
|
|
+ password: "password",
|
|
|
+ });
|
|
|
+ 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 user/login - missing fields returns error", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ const { handler } = await import("../../routes/api/user/login.tsx");
|
|
|
+ const ctx = makeCtx("POST", { email: "test@example.com" });
|
|
|
+ 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 user/login GET - returns user info with valid token", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ const user = await seedUser("getuser@example.com", "password");
|
|
|
+ const userId = user[0]["id"] as string | number;
|
|
|
+ const token = await seedToken(userId);
|
|
|
+
|
|
|
+ const { handler } = await import("../../routes/api/user/login.tsx");
|
|
|
+ const ctx = makeCtx("GET", undefined, `pd-user-token=${token}`);
|
|
|
+ const res = await handler.GET!(ctx as any);
|
|
|
+ const body = await res.json();
|
|
|
+ assertEquals(body.success, true);
|
|
|
+ assertEquals(body.data.name, "getuser");
|
|
|
+ assertEquals(body.data.email, "getuser@example.com");
|
|
|
+ } finally {
|
|
|
+ Deno.env.delete("POSTDOWN_DB_PATH");
|
|
|
+ cleanup(dbPath);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+Deno.test("API user/login GET - returns error without token", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ const { handler } = await import("../../routes/api/user/login.tsx");
|
|
|
+ const ctx = makeCtx("GET");
|
|
|
+ const res = await handler.GET!(ctx as any);
|
|
|
+ const body = await res.json();
|
|
|
+ assertEquals(body.success, false);
|
|
|
+ } finally {
|
|
|
+ Deno.env.delete("POSTDOWN_DB_PATH");
|
|
|
+ cleanup(dbPath);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+Deno.test("API user/logout - returns success and clears cookie", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ const { handler } = await import("../../routes/api/user/logout.tsx");
|
|
|
+ const ctx = makeCtx("GET");
|
|
|
+ const res = await handler.GET!(ctx as any);
|
|
|
+ const body = await res.json();
|
|
|
+ assertEquals(body.success, true);
|
|
|
+
|
|
|
+ const setCookie = res.headers.get("set-cookie");
|
|
|
+ assertEquals(setCookie !== null, true);
|
|
|
+ assertEquals(setCookie!.includes("pd-user-token="), true);
|
|
|
+ } finally {
|
|
|
+ Deno.env.delete("POSTDOWN_DB_PATH");
|
|
|
+ cleanup(dbPath);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+Deno.test("API user/reset - successful password reset", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ const user = await seedUser("reset@example.com", "oldpassword");
|
|
|
+ const userId = user[0]["id"] as string | number;
|
|
|
+ const token = await seedToken(userId);
|
|
|
+
|
|
|
+ const { handler } = await import("../../routes/api/user/reset.tsx");
|
|
|
+ const ctx = makeCtx("POST", {
|
|
|
+ old: "oldpassword",
|
|
|
+ new: "newpassword",
|
|
|
+ }, `pd-user-token=${token}`);
|
|
|
+ const res = await handler.POST!(ctx as any);
|
|
|
+ const body = await res.json();
|
|
|
+ assertEquals(body.success, true);
|
|
|
+ } finally {
|
|
|
+ Deno.env.delete("POSTDOWN_DB_PATH");
|
|
|
+ cleanup(dbPath);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+Deno.test("API user/reset - wrong old password returns error", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ const user = await seedUser("resetwrong@example.com", "oldpassword");
|
|
|
+ const userId = user[0]["id"] as string | number;
|
|
|
+ const token = await seedToken(userId);
|
|
|
+
|
|
|
+ const { handler } = await import("../../routes/api/user/reset.tsx");
|
|
|
+ const ctx = makeCtx("POST", {
|
|
|
+ old: "wrongpassword",
|
|
|
+ new: "newpassword",
|
|
|
+ }, `pd-user-token=${token}`);
|
|
|
+ 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 user/reset - without token returns error", async () => {
|
|
|
+ const dbPath = getTestDbPath();
|
|
|
+ Deno.env.set("POSTDOWN_DB_PATH", dbPath);
|
|
|
+ try {
|
|
|
+ const { handler } = await import("../../routes/api/user/reset.tsx");
|
|
|
+ const ctx = makeCtx("POST", {
|
|
|
+ old: "oldpassword",
|
|
|
+ new: "newpassword",
|
|
|
+ });
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+});
|