user_test.ts 8.4 KB

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