user_test.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. import { assertEquals } from "@std/assert";
  2. import { find, insert } from "utils/db.ts";
  3. import { getCryptoString } from "utils/server.ts";
  4. let testCounter = 0;
  5. function getTestDbPath() {
  6. testCounter++;
  7. return `data/test_api_user_${testCounter}_${Date.now()}.db`;
  8. }
  9. function cleanup(dbPath: string) {
  10. try {
  11. Deno.removeSync(dbPath);
  12. } catch {
  13. // File may not exist
  14. }
  15. }
  16. function makeCtx(method: string, body?: object, cookie?: string) {
  17. const headers: Record<string, string> = { "Content-Type": "application/json" };
  18. if (cookie) headers["Cookie"] = cookie;
  19. const req = new Request("http://localhost", {
  20. method,
  21. headers,
  22. body: method === "GET" ? undefined : JSON.stringify(body || {}),
  23. });
  24. return { req };
  25. }
  26. async function seedUser(email: string, password: string) {
  27. const hashedPw = await getCryptoString(password, "MD5");
  28. const user = insert("User", {
  29. name: email.split("@")[0],
  30. email,
  31. password: hashedPw,
  32. });
  33. return user;
  34. }
  35. async function seedToken(userId: string | number) {
  36. const token = await getCryptoString("test-token-" + userId + Date.now(), "MD5");
  37. insert("Token", { token, user_id: userId });
  38. return token;
  39. }
  40. Deno.test("API user/register - successful registration", async () => {
  41. const dbPath = getTestDbPath();
  42. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  43. try {
  44. const { handler } = await import("../../routes/api/user/register.tsx");
  45. const ctx = makeCtx("POST", {
  46. email: "newuser@example.com",
  47. password: "password123",
  48. });
  49. const res = await handler.POST!(ctx as any);
  50. const body = await res.json();
  51. assertEquals(body.success, true);
  52. assertEquals(body.data, true);
  53. const users = find("User", { email: "newuser@example.com" }, ["id"]);
  54. assertEquals(users.length, 1);
  55. } finally {
  56. Deno.env.delete("POSTDOWN_DB_PATH");
  57. cleanup(dbPath);
  58. }
  59. });
  60. Deno.test("API user/register - missing email returns error", async () => {
  61. const dbPath = getTestDbPath();
  62. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  63. try {
  64. const { handler } = await import("../../routes/api/user/register.tsx");
  65. const ctx = makeCtx("POST", { password: "password123" });
  66. const res = await handler.POST!(ctx as any);
  67. const body = await res.json();
  68. assertEquals(body.success, false);
  69. } finally {
  70. Deno.env.delete("POSTDOWN_DB_PATH");
  71. cleanup(dbPath);
  72. }
  73. });
  74. Deno.test("API user/register - missing password returns error", async () => {
  75. const dbPath = getTestDbPath();
  76. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  77. try {
  78. const { handler } = await import("../../routes/api/user/register.tsx");
  79. const ctx = makeCtx("POST", { email: "test@example.com" });
  80. const res = await handler.POST!(ctx as any);
  81. const body = await res.json();
  82. assertEquals(body.success, false);
  83. } finally {
  84. Deno.env.delete("POSTDOWN_DB_PATH");
  85. cleanup(dbPath);
  86. }
  87. });
  88. Deno.test("API user/login - successful login", async () => {
  89. const dbPath = getTestDbPath();
  90. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  91. try {
  92. await seedUser("login@example.com", "mypassword");
  93. const { handler } = await import("../../routes/api/user/login.tsx");
  94. const ctx = makeCtx("POST", {
  95. email: "login@example.com",
  96. password: "mypassword",
  97. });
  98. const res = await handler.POST!(ctx as any);
  99. const body = await res.json();
  100. assertEquals(body.success, true);
  101. assertEquals(body.data, true);
  102. const setCookie = res.headers.get("set-cookie");
  103. assertEquals(setCookie !== null, true);
  104. assertEquals(setCookie!.includes("pd-user-token="), true);
  105. } finally {
  106. Deno.env.delete("POSTDOWN_DB_PATH");
  107. cleanup(dbPath);
  108. }
  109. });
  110. Deno.test("API user/login - wrong email returns error", async () => {
  111. const dbPath = getTestDbPath();
  112. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  113. try {
  114. const { handler } = await import("../../routes/api/user/login.tsx");
  115. const ctx = makeCtx("POST", {
  116. email: "nonexistent@example.com",
  117. password: "password",
  118. });
  119. const res = await handler.POST!(ctx as any);
  120. const body = await res.json();
  121. assertEquals(body.success, false);
  122. } finally {
  123. Deno.env.delete("POSTDOWN_DB_PATH");
  124. cleanup(dbPath);
  125. }
  126. });
  127. Deno.test("API user/login - missing fields returns error", async () => {
  128. const dbPath = getTestDbPath();
  129. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  130. try {
  131. const { handler } = await import("../../routes/api/user/login.tsx");
  132. const ctx = makeCtx("POST", { email: "test@example.com" });
  133. const res = await handler.POST!(ctx as any);
  134. const body = await res.json();
  135. assertEquals(body.success, false);
  136. } finally {
  137. Deno.env.delete("POSTDOWN_DB_PATH");
  138. cleanup(dbPath);
  139. }
  140. });
  141. Deno.test("API user/login GET - returns user info with valid token", async () => {
  142. const dbPath = getTestDbPath();
  143. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  144. try {
  145. const user = await seedUser("getuser@example.com", "password");
  146. const userId = user[0]["id"] as string | number;
  147. const token = await seedToken(userId);
  148. const { handler } = await import("../../routes/api/user/login.tsx");
  149. const ctx = makeCtx("GET", undefined, `pd-user-token=${token}`);
  150. const res = await handler.GET!(ctx as any);
  151. const body = await res.json();
  152. assertEquals(body.success, true);
  153. assertEquals(body.data.name, "getuser");
  154. assertEquals(body.data.email, "getuser@example.com");
  155. } finally {
  156. Deno.env.delete("POSTDOWN_DB_PATH");
  157. cleanup(dbPath);
  158. }
  159. });
  160. Deno.test("API user/login GET - returns error without token", async () => {
  161. const dbPath = getTestDbPath();
  162. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  163. try {
  164. const { handler } = await import("../../routes/api/user/login.tsx");
  165. const ctx = makeCtx("GET");
  166. const res = await handler.GET!(ctx as any);
  167. const body = await res.json();
  168. assertEquals(body.success, false);
  169. } finally {
  170. Deno.env.delete("POSTDOWN_DB_PATH");
  171. cleanup(dbPath);
  172. }
  173. });
  174. Deno.test("API user/logout - returns success and clears cookie", async () => {
  175. const dbPath = getTestDbPath();
  176. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  177. try {
  178. const { handler } = await import("../../routes/api/user/logout.tsx");
  179. const ctx = makeCtx("GET");
  180. const res = await handler.GET!(ctx as any);
  181. const body = await res.json();
  182. assertEquals(body.success, true);
  183. const setCookie = res.headers.get("set-cookie");
  184. assertEquals(setCookie !== null, true);
  185. assertEquals(setCookie!.includes("pd-user-token="), true);
  186. } finally {
  187. Deno.env.delete("POSTDOWN_DB_PATH");
  188. cleanup(dbPath);
  189. }
  190. });
  191. Deno.test("API user/reset - successful password reset", async () => {
  192. const dbPath = getTestDbPath();
  193. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  194. try {
  195. const user = await seedUser("reset@example.com", "oldpassword");
  196. const userId = user[0]["id"] as string | number;
  197. const token = await seedToken(userId);
  198. const { handler } = await import("../../routes/api/user/reset.tsx");
  199. const ctx = makeCtx("POST", {
  200. old: "oldpassword",
  201. new: "newpassword",
  202. }, `pd-user-token=${token}`);
  203. const res = await handler.POST!(ctx as any);
  204. const body = await res.json();
  205. assertEquals(body.success, true);
  206. } finally {
  207. Deno.env.delete("POSTDOWN_DB_PATH");
  208. cleanup(dbPath);
  209. }
  210. });
  211. Deno.test("API user/reset - wrong old password returns error", async () => {
  212. const dbPath = getTestDbPath();
  213. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  214. try {
  215. const user = await seedUser("resetwrong@example.com", "oldpassword");
  216. const userId = user[0]["id"] as string | number;
  217. const token = await seedToken(userId);
  218. const { handler } = await import("../../routes/api/user/reset.tsx");
  219. const ctx = makeCtx("POST", {
  220. old: "wrongpassword",
  221. new: "newpassword",
  222. }, `pd-user-token=${token}`);
  223. const res = await handler.POST!(ctx as any);
  224. const body = await res.json();
  225. assertEquals(body.success, false);
  226. } finally {
  227. Deno.env.delete("POSTDOWN_DB_PATH");
  228. cleanup(dbPath);
  229. }
  230. });
  231. Deno.test("API user/reset - without token returns error", async () => {
  232. const dbPath = getTestDbPath();
  233. Deno.env.set("POSTDOWN_DB_PATH", dbPath);
  234. try {
  235. const { handler } = await import("../../routes/api/user/reset.tsx");
  236. const ctx = makeCtx("POST", {
  237. old: "oldpassword",
  238. new: "newpassword",
  239. });
  240. const res = await handler.POST!(ctx as any);
  241. const body = await res.json();
  242. assertEquals(body.success, false);
  243. } finally {
  244. Deno.env.delete("POSTDOWN_DB_PATH");
  245. cleanup(dbPath);
  246. }
  247. });