diff --git a/.github/codecov.yml b/.github/codecov.yml index de10b6ff3..8a3f42a71 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -6,4 +6,8 @@ coverage: informational: true patch: default: - informational: true \ No newline at end of file + informational: true +ignore: + - "components" + - "islands" + - "routes" \ No newline at end of file diff --git a/e2e_test.ts b/e2e_test.ts new file mode 100644 index 000000000..2b172a4f1 --- /dev/null +++ b/e2e_test.ts @@ -0,0 +1,161 @@ +// Copyright 2023 the Deno authors. All rights reserved. MIT license. + +import { createHandler } from "$fresh/server.ts"; +import manifest from "@/fresh.gen.ts"; +import { + assert, + assertEquals, + assertFalse, + assertInstanceOf, + assertStringIncludes, +} from "std/testing/asserts.ts"; +import type { ConnInfo } from "std/http/server.ts"; + +const CONN_INFO: ConnInfo = { + localAddr: { hostname: "localhost", port: 8000, transport: "tcp" }, + remoteAddr: { hostname: "localhost", port: 53496, transport: "tcp" }, +}; + +Deno.test("[http]", async (test) => { + const handler = await createHandler(manifest); + + await test.step("GET /", async () => { + const response = await handler(new Request("http://localhost"), CONN_INFO); + + assert(response.ok); + assertInstanceOf(response.body, ReadableStream); + assertEquals( + response.headers.get("content-type"), + "text/html; charset=utf-8", + ); + assertEquals(response.status, 200); + }); + + await test.step("GET /account", async () => { + const response = await handler( + new Request("http://localhost/account"), + CONN_INFO, + ); + + assertFalse(response.ok); + assertFalse(response.body); + assertEquals( + response.headers.get("location"), + "/signin?from=http://localhost/account", + ); + assertEquals(response.status, 303); + }); + + await test.step("GET /callback", async () => { + const response = await handler( + new Request("http://localhost/callback"), + CONN_INFO, + ); + + assertFalse(response.ok); + assertInstanceOf(response.body, ReadableStream); + assertEquals( + response.headers.get("content-type"), + "text/html; charset=utf-8", + ); + assertEquals(response.status, 500); + }); + + await test.step("GET /blog", async () => { + const response = await handler( + new Request("http://localhost/blog"), + CONN_INFO, + ); + + assert(response.ok); + assertInstanceOf(response.body, ReadableStream); + assertEquals( + response.headers.get("content-type"), + "text/html; charset=utf-8", + ); + assertEquals(response.status, 200); + }); + + await test.step("GET /pricing", async () => { + const response = await handler( + new Request("http://localhost/pricing"), + CONN_INFO, + ); + + assertFalse(response.ok); + assertInstanceOf(response.body, ReadableStream); + assertEquals( + response.headers.get("content-type"), + "text/html; charset=utf-8", + ); + assertEquals(response.status, 404); + }); + + await test.step("GET /signin", async () => { + const response = await handler( + new Request("http://localhost/signin"), + CONN_INFO, + ); + + assertFalse(response.ok); + assertFalse(response.body); + assertStringIncludes( + response.headers.get("location")!, + "https://github.com/login/oauth/authorize", + ); + assertEquals(response.status, 302); + }); + + await test.step("GET /signout", async () => { + const response = await handler( + new Request("http://localhost/signout"), + CONN_INFO, + ); + + assertFalse(response.ok); + assertFalse(response.body); + assertEquals(response.headers.get("location"), "/"); + assertEquals(response.status, 302); + }); + + await test.step("GET /stats", async () => { + const response = await handler( + new Request("http://localhost/stats"), + CONN_INFO, + ); + + assert(response.ok); + assertInstanceOf(response.body, ReadableStream); + assertEquals( + response.headers.get("content-type"), + "text/html; charset=utf-8", + ); + assertEquals(response.status, 200); + }); + + await test.step("GET /submit", async () => { + const response = await handler( + new Request("http://localhost/submit"), + CONN_INFO, + ); + + assertFalse(response.ok); + assertFalse(response.body); + assertEquals( + response.headers.get("location"), + "/signin?from=http://localhost/submit", + ); + assertEquals(response.status, 303); + }); + + await test.step("POST /submit", async () => { + const response = await handler( + new Request("http://localhost/submit", { method: "POST" }), + CONN_INFO, + ); + + assertFalse(response.ok); + assertFalse(response.body); + assertEquals(response.status, 401); + }); +}); diff --git a/utils/db.ts b/utils/db.ts index d032c576d..3335987d2 100644 --- a/utils/db.ts +++ b/utils/db.ts @@ -268,13 +268,6 @@ export async function createVote(vote: Vote) { export async function deleteVote(vote: Vote) { vote.item.score--; - const itemKey = ["items", vote.item.id]; - const itemsByTimeKey = [ - "items_by_time", - vote.item.createdAt.getTime(), - vote.item.id, - ]; - const itemsByUserKey = ["items_by_user", vote.item.userId, vote.item.id]; const votedItemsByUserKey = [ "voted_items_by_user", vote.user.id, @@ -286,11 +279,29 @@ export async function deleteVote(vote: Vote) { vote.user.id, ]; + const [votedItemsByUserRes, votedUsersByItemRes] = await kv.getMany([ + votedItemsByUserKey, + votedUsersByItemKey, + ]); + + if (!votedItemsByUserRes.value || !votedUsersByItemRes.value) { + throw new Error(`Failed to delete vote: ${vote}`); + } + + const itemKey = ["items", vote.item.id]; + const itemsByTimeKey = [ + "items_by_time", + vote.item.createdAt.getTime(), + vote.item.id, + ]; + const itemsByUserKey = ["items_by_user", vote.item.userId, vote.item.id]; + const [itemRes, itemsByTimeRes, itemsByUserRes] = await kv.getMany([ itemKey, itemsByTimeKey, itemsByUserKey, ]); + const res = await kv.atomic() .check(itemRes) .check(itemsByTimeRes)