diff options
Diffstat (limited to 'packages/e2e_tests/tests/api')
| -rw-r--r-- | packages/e2e_tests/tests/api/assets.test.ts | 4 | ||||
| -rw-r--r-- | packages/e2e_tests/tests/api/bookmarks.test.ts | 274 | ||||
| -rw-r--r-- | packages/e2e_tests/tests/api/public.test.ts | 322 | ||||
| -rw-r--r-- | packages/e2e_tests/tests/api/rss.test.ts | 155 | ||||
| -rw-r--r-- | packages/e2e_tests/tests/api/tags.test.ts | 27 |
5 files changed, 724 insertions, 58 deletions
diff --git a/packages/e2e_tests/tests/api/assets.test.ts b/packages/e2e_tests/tests/api/assets.test.ts index 5c294929..78a5c7fe 100644 --- a/packages/e2e_tests/tests/api/assets.test.ts +++ b/packages/e2e_tests/tests/api/assets.test.ts @@ -39,7 +39,7 @@ describe("Assets API", () => { // Retrieve the asset const resp = await fetch( - `http://localhost:${port}/api/assets/${uploadResponse.assetId}`, + `http://localhost:${port}/api/v1/assets/${uploadResponse.assetId}`, { headers: { authorization: `Bearer ${apiKey}`, @@ -123,7 +123,7 @@ describe("Assets API", () => { // Verify asset is deleted const assetResponse = await fetch( - `http://localhost:${port}/api/assets/${uploadResponse.assetId}`, + `http://localhost:${port}/api/v1/assets/${uploadResponse.assetId}`, { headers: { authorization: `Bearer ${apiKey}`, diff --git a/packages/e2e_tests/tests/api/bookmarks.test.ts b/packages/e2e_tests/tests/api/bookmarks.test.ts index 6c56f689..d40c1add 100644 --- a/packages/e2e_tests/tests/api/bookmarks.test.ts +++ b/packages/e2e_tests/tests/api/bookmarks.test.ts @@ -397,54 +397,258 @@ describe("Bookmarks API", () => { expect(finalPage!.nextCursor).toBeNull(); }); - it("should support precrawling via singlefile", async () => { - const file = new File(["<html>HELLO WORLD</html>"], "test.html", { - type: "text/html", - }); + describe("singlefile", () => { + async function uploadSinglefileAsset(ifexists?: string) { + const file = new File(["<html>HELLO WORLD</html>"], "test.html", { + type: "text/html", + }); - const formData = new FormData(); - formData.append("url", "https://example.com"); - formData.append("file", file); + const formData = new FormData(); + formData.append("url", "https://example.com"); + formData.append("file", file); - // OpenAPI typescript doesn't support multipart/form-data - // Upload the singlefile archive - const response = await fetch( - `http://localhost:${port}/api/v1/bookmarks/singlefile`, - { + const url = new URL( + `http://localhost:${port}/api/v1/bookmarks/singlefile`, + ); + if (ifexists) { + url.searchParams.append("ifexists", ifexists); + } + + const response = await fetch(url.toString(), { method: "POST", headers: { authorization: `Bearer ${apiKey}`, }, body: formData, - }, - ); + }); - if (!response.ok) { - throw new Error(`Failed to upload asset: ${response.statusText}`); + if (!response.ok) { + return [null, response] as const; + } + + const data = (await response.json()) as { id: string }; + return [data, response] as const; } - expect(response.status).toBe(201); + it("should support precrawling via singlefile with ifexists=skip", async () => { + // First upload: create a bookmark + const [data, response] = await uploadSinglefileAsset(); + expect(response?.status).toBe(201); + const bookmarkId = data?.id; + if (!bookmarkId) throw new Error("Bookmark ID not found"); - const { id: bookmarkId } = (await response.json()) as { - id: string; - }; + // Get the bookmark and record the precrawled asset id + const { data: bookmark, response: getResponse1 } = await client.GET( + "/bookmarks/{bookmarkId}", + { + params: { path: { bookmarkId } }, + }, + ); + expect(getResponse1.status).toBe(200); + const assetIds = bookmark!.assets + .filter((a) => a.assetType === "precrawledArchive") + .map((a) => a.id); + expect(assetIds.length).toBe(1); + const firstAssetId = assetIds[0]; - // Get the created bookmark - const { data: retrievedBookmark, response: getResponse } = await client.GET( - "/bookmarks/{bookmarkId}", - { - params: { - path: { - bookmarkId: bookmarkId, - }, + // Second upload with skip + const [data2, response2] = await uploadSinglefileAsset("skip"); + expect(response2?.status).toBe(200); + expect(data2?.id).toBe(bookmarkId); + + // Get the bookmark again + const { data: bookmark2, response: getResponse2 } = await client.GET( + "/bookmarks/{bookmarkId}", + { + params: { path: { bookmarkId } }, }, - }, - ); + ); + expect(getResponse2.status).toBe(200); + const assetIds2 = bookmark2!.assets + .filter((a) => a.assetType === "precrawledArchive") + .map((a) => a.id); + expect(assetIds2).toEqual([firstAssetId]); // same asset + }); - expect(getResponse.status).toBe(200); - assert(retrievedBookmark!.content.type === "link"); - expect(retrievedBookmark!.assets.map((a) => a.assetType)).toContain( - "precrawledArchive", - ); + it("should support precrawling via singlefile with ifexists=overwrite", async () => { + // First upload + const [data, response] = await uploadSinglefileAsset("overwrite"); + expect(response?.status).toBe(201); + const bookmarkId = data?.id; + if (!bookmarkId) throw new Error("Bookmark ID not found"); + + // Record the asset + const { data: bookmark, response: getResponse1 } = await client.GET( + "/bookmarks/{bookmarkId}", + { + params: { path: { bookmarkId } }, + }, + ); + expect(getResponse1.status).toBe(200); + const firstAssetId = bookmark!.assets.find( + (a) => a.assetType === "precrawledArchive", + )?.id; + expect(firstAssetId).toBeDefined(); + + // Second upload with overwrite + const [data2, response2] = await uploadSinglefileAsset("overwrite"); + expect(response2?.status).toBe(200); + expect(data2?.id).toBe(bookmarkId); + + // Get the bookmark again + const { data: bookmark2, response: getResponse2 } = await client.GET( + "/bookmarks/{bookmarkId}", + { + params: { path: { bookmarkId } }, + }, + ); + expect(getResponse2.status).toBe(200); + const secondAssetId = bookmark2!.assets.find( + (a) => a.assetType === "precrawledArchive", + )?.id; + expect(secondAssetId).toBeDefined(); + expect(secondAssetId).not.toBe(firstAssetId); + // There should be only one precrawledArchive asset + const precrawledAssets = bookmark2!.assets.filter( + (a) => a.assetType === "precrawledArchive", + ); + expect(precrawledAssets.length).toBe(1); + }); + + it("should support precrawling via singlefile with ifexists=overwrite-recrawl", async () => { + // First upload + const [data, response] = await uploadSinglefileAsset("overwrite-recrawl"); + expect(response?.status).toBe(201); + const bookmarkId = data?.id; + if (!bookmarkId) throw new Error("Bookmark ID not found"); + + // Record the asset + const { data: bookmark, response: getResponse1 } = await client.GET( + "/bookmarks/{bookmarkId}", + { + params: { path: { bookmarkId } }, + }, + ); + expect(getResponse1.status).toBe(200); + const firstAssetId = bookmark!.assets.find( + (a) => a.assetType === "precrawledArchive", + )?.id; + expect(firstAssetId).toBeDefined(); + + // Second upload with overwrite-recrawl + const [data2, response2] = + await uploadSinglefileAsset("overwrite-recrawl"); + expect(response2?.status).toBe(200); + expect(data2?.id).toBe(bookmarkId); + + // Get the bookmark again + const { data: bookmark2, response: getResponse2 } = await client.GET( + "/bookmarks/{bookmarkId}", + { + params: { path: { bookmarkId } }, + }, + ); + expect(getResponse2.status).toBe(200); + const secondAssetId = bookmark2!.assets.find( + (a) => a.assetType === "precrawledArchive", + )?.id; + expect(secondAssetId).toBeDefined(); + expect(secondAssetId).not.toBe(firstAssetId); + // There should be only one precrawledArchive asset + const precrawledAssets = bookmark2!.assets.filter( + (a) => a.assetType === "precrawledArchive", + ); + expect(precrawledAssets.length).toBe(1); + }); + + it("should support precrawling via singlefile with ifexists=append", async () => { + // First upload + const [data, response] = await uploadSinglefileAsset("append"); + expect(response?.status).toBe(201); + const bookmarkId = data?.id; + if (!bookmarkId) throw new Error("Bookmark ID not found"); + + // Record the first asset + const { data: bookmark, response: getResponse1 } = await client.GET( + "/bookmarks/{bookmarkId}", + { + params: { path: { bookmarkId } }, + }, + ); + expect(getResponse1.status).toBe(200); + const firstAssetId = bookmark!.assets.find( + (a) => a.assetType === "precrawledArchive", + )?.id; + expect(firstAssetId).toBeDefined(); + + // Second upload with append + const [data2, response2] = await uploadSinglefileAsset("append"); + expect(response2?.status).toBe(200); + expect(data2?.id).toBe(bookmarkId); + + // Get the bookmark again + const { data: bookmark2, response: getResponse2 } = await client.GET( + "/bookmarks/{bookmarkId}", + { + params: { path: { bookmarkId } }, + }, + ); + expect(getResponse2.status).toBe(200); + const precrawledAssets = bookmark2!.assets.filter( + (a) => a.assetType === "precrawledArchive", + ); + expect(precrawledAssets.length).toBe(2); + expect(precrawledAssets.map((a) => a.id)).toContain(firstAssetId); + // The second asset id should be different + const secondAssetId = precrawledAssets.find( + (asset) => asset.id !== firstAssetId, + )?.id; + expect(secondAssetId).toBeDefined(); + }); + + it("should support precrawling via singlefile with ifexists=append-recrawl", async () => { + // First upload + const [data, response] = await uploadSinglefileAsset("append-recrawl"); + expect(response?.status).toBe(201); + const bookmarkId = data?.id; + if (!bookmarkId) throw new Error("Bookmark ID not found"); + + // Record the first asset + const { data: bookmark, response: getResponse1 } = await client.GET( + "/bookmarks/{bookmarkId}", + { + params: { path: { bookmarkId } }, + }, + ); + expect(getResponse1.status).toBe(200); + const firstAssetId = bookmark!.assets.find( + (a) => a.assetType === "precrawledArchive", + )?.id; + expect(firstAssetId).toBeDefined(); + + // Second upload with append-recrawl + const [data2, response2] = await uploadSinglefileAsset("append-recrawl"); + expect(response2?.status).toBe(200); + expect(data2?.id).toBe(bookmarkId); + + // Get the bookmark again + const { data: bookmark2, response: getResponse2 } = await client.GET( + "/bookmarks/{bookmarkId}", + { + params: { path: { bookmarkId } }, + }, + ); + expect(getResponse2.status).toBe(200); + const precrawledAssets = bookmark2!.assets.filter( + (a) => a.assetType === "precrawledArchive", + ); + expect(precrawledAssets.length).toBe(2); + expect(precrawledAssets.map((a) => a.id)).toContain(firstAssetId); + // The second asset id should be different + const secondAssetId = precrawledAssets.find( + (asset) => asset.id !== firstAssetId, + )?.id; + expect(secondAssetId).toBeDefined(); + }); }); }); diff --git a/packages/e2e_tests/tests/api/public.test.ts b/packages/e2e_tests/tests/api/public.test.ts new file mode 100644 index 00000000..54ef79ea --- /dev/null +++ b/packages/e2e_tests/tests/api/public.test.ts @@ -0,0 +1,322 @@ +import { assert, beforeEach, describe, expect, inject, it } from "vitest"; +import { z } from "zod"; + +import { createSignedToken } from "../../../shared/signedTokens"; +import { zAssetSignedTokenSchema } from "../../../shared/types/assets"; +import { BookmarkTypes } from "../../../shared/types/bookmarks"; +import { createTestUser, uploadTestAsset } from "../../utils/api"; +import { waitUntil } from "../../utils/general"; +import { getTrpcClient } from "../../utils/trpc"; + +describe("Public API", () => { + const port = inject("karakeepPort"); + + if (!port) { + throw new Error("Missing required environment variables"); + } + + let apiKey: string; // For the primary test user + + async function seedDatabase(currentApiKey: string) { + const trpcClient = getTrpcClient(currentApiKey); + + // Create two lists + const publicList = await trpcClient.lists.create.mutate({ + name: "Public List", + icon: "🚀", + type: "manual", + }); + + await trpcClient.lists.edit.mutate({ + listId: publicList.id, + public: true, + }); + + // Create two bookmarks + const createBookmark1 = await trpcClient.bookmarks.createBookmark.mutate({ + title: "Test Bookmark #1", + url: "http://nginx:80/hello.html", + type: BookmarkTypes.LINK, + }); + + // Create a second bookmark with an asset + const file = new File(["test content"], "test.pdf", { + type: "application/pdf", + }); + + const uploadResponse = await uploadTestAsset(currentApiKey, port, file); + const createBookmark2 = await trpcClient.bookmarks.createBookmark.mutate({ + title: "Test Bookmark #2", + type: BookmarkTypes.ASSET, + assetType: "pdf", + assetId: uploadResponse.assetId, + }); + + await trpcClient.lists.addToList.mutate({ + listId: publicList.id, + bookmarkId: createBookmark1.id, + }); + await trpcClient.lists.addToList.mutate({ + listId: publicList.id, + bookmarkId: createBookmark2.id, + }); + + return { publicList, createBookmark1, createBookmark2 }; + } + + beforeEach(async () => { + apiKey = await createTestUser(); + }); + + it("should get public bookmarks", async () => { + const { publicList } = await seedDatabase(apiKey); + const trpcClient = getTrpcClient(apiKey); + + const res = await trpcClient.publicBookmarks.getPublicBookmarksInList.query( + { + listId: publicList.id, + }, + ); + + expect(res.bookmarks.length).toBe(2); + }); + + it("should be able to access the assets of the public bookmarks", async () => { + const { publicList, createBookmark1, createBookmark2 } = + await seedDatabase(apiKey); + + const trpcClient = getTrpcClient(apiKey); + // Wait for link bookmark to be crawled and have a banner image (screenshot) + await waitUntil( + async () => { + const res = await trpcClient.bookmarks.getBookmark.query({ + bookmarkId: createBookmark1.id, + }); + assert(res.content.type === BookmarkTypes.LINK); + // Check for screenshotAssetId as bannerImageUrl might be derived from it or original imageUrl + return !!res.content.screenshotAssetId || !!res.content.imageUrl; + }, + "Bookmark is crawled and has banner info", + 20000, // Increased timeout as crawling can take time + ); + + const res = await trpcClient.publicBookmarks.getPublicBookmarksInList.query( + { + listId: publicList.id, + }, + ); + + const b1Resp = res.bookmarks.find((b) => b.id === createBookmark1.id); + expect(b1Resp).toBeDefined(); + const b2Resp = res.bookmarks.find((b) => b.id === createBookmark2.id); + expect(b2Resp).toBeDefined(); + + assert(b1Resp!.content.type === BookmarkTypes.LINK); + assert(b2Resp!.content.type === BookmarkTypes.ASSET); + + { + // Banner image fetch for link bookmark + assert( + b1Resp!.bannerImageUrl, + "Link bookmark should have a bannerImageUrl", + ); + const assetFetch = await fetch(b1Resp!.bannerImageUrl); + expect(assetFetch.status).toBe(200); + } + + { + // Actual asset fetch for asset bookmark + assert( + b2Resp!.content.assetUrl, + "Asset bookmark should have an assetUrl", + ); + const assetFetch = await fetch(b2Resp!.content.assetUrl); + expect(assetFetch.status).toBe(200); + } + }); + + it("Accessing non public list should fail", async () => { + const trpcClient = getTrpcClient(apiKey); + const nonPublicList = await trpcClient.lists.create.mutate({ + name: "Non Public List", + icon: "🚀", + type: "manual", + }); + + await expect( + trpcClient.publicBookmarks.getPublicBookmarksInList.query({ + listId: nonPublicList.id, + }), + ).rejects.toThrow(/List not found/); + }); + + describe("Public asset token validation", () => { + let userId: string; + let assetId: string; // Asset belonging to the primary user (userId) + + beforeEach(async () => { + const trpcClient = getTrpcClient(apiKey); + const whoami = await trpcClient.users.whoami.query(); + userId = whoami.id; + const assetUpload = await uploadTestAsset( + apiKey, + port, + new File(["test content for token validation"], "token_test.pdf", { + type: "application/pdf", + }), + ); + assetId = assetUpload.assetId; + }); + + it("should succeed with a valid token", async () => { + const token = createSignedToken( + { + assetId, + userId, + } as z.infer<typeof zAssetSignedTokenSchema>, + Date.now() + 60000, // Expires in 60 seconds + ); + const res = await fetch( + `http://localhost:${port}/api/public/assets/${assetId}?token=${token}`, + ); + expect(res.status).toBe(200); + expect((await res.blob()).type).toBe("application/pdf"); + }); + + it("should fail without a token", async () => { + const res = await fetch( + `http://localhost:${port}/api/public/assets/${assetId}`, + ); + expect(res.status).toBe(400); // Bad Request due to missing token query param + }); + + it("should fail with a malformed token string (e.g., not base64)", async () => { + const res = await fetch( + `http://localhost:${port}/api/public/assets/${assetId}?token=thisIsNotValidBase64!@#`, + ); + expect(res.status).toBe(403); + expect(await res.json()).toEqual( + expect.objectContaining({ error: "Invalid or expired token" }), + ); + }); + + it("should fail with a token having a structurally invalid inner payload", async () => { + // Payload that doesn't conform to zAssetSignedTokenSchema (e.g. misspelled key) + const malformedInnerPayload = { + asset_id_mispelled: assetId, + userId: userId, + }; + const token = createSignedToken( + malformedInnerPayload, + Date.now() + 60000, + ); + const res = await fetch( + `http://localhost:${port}/api/public/assets/${assetId}?token=${token}`, + ); + expect(res.status).toBe(403); + expect(await res.json()).toEqual( + expect.objectContaining({ error: "Invalid or expired token" }), + ); + }); + + it("should fail after token expiry", async () => { + const token = createSignedToken( + { + assetId, + userId, + } as z.infer<typeof zAssetSignedTokenSchema>, + Date.now() + 1000, // Expires in 1 second + ); + + // Wait for more than 1 second to ensure expiry + await new Promise((resolve) => setTimeout(resolve, 2000)); + + const res = await fetch( + `http://localhost:${port}/api/public/assets/${assetId}?token=${token}`, + ); + expect(res.status).toBe(403); + expect(await res.json()).toEqual( + expect.objectContaining({ error: "Invalid or expired token" }), + ); + }); + + it("should fail when using a valid token for a different asset", async () => { + const anotherAssetUpload = await uploadTestAsset( + apiKey, // Same user + port, + new File(["other content"], "other_asset.pdf", { + type: "application/pdf", + }), + ); + const anotherAssetId = anotherAssetUpload.assetId; + + // Token is valid for 'anotherAssetId' + const tokenForAnotherAsset = createSignedToken( + { + assetId: anotherAssetId, + userId, + } as z.infer<typeof zAssetSignedTokenSchema>, + Date.now() + 60000, + ); + + // Attempt to use this token to access the original 'assetId' + const res = await fetch( + `http://localhost:${port}/api/public/assets/${assetId}?token=${tokenForAnotherAsset}`, + ); + expect(res.status).toBe(403); + expect(await res.json()).toEqual( + expect.objectContaining({ error: "Invalid or expired token" }), + ); + }); + + it("should fail if token's userId does not own the requested assetId (expect 404)", async () => { + // User1 (primary, `apiKey`, `userId`) owns `assetId` (from beforeEach) + + // Create User2 - ensure unique email for user creation + const apiKeyUser2 = await createTestUser(); + const trpcClientUser2 = getTrpcClient(apiKeyUser2); + const whoamiUser2 = await trpcClientUser2.users.whoami.query(); + const userIdUser2 = whoamiUser2.id; + + // Generate a token where the payload claims assetId is being accessed by userIdUser2, + // but assetId actually belongs to the original userId. + const tokenForUser2AttemptingAsset1 = createSignedToken( + { + assetId: assetId, // assetId belongs to user1 (userId) + userId: userIdUser2, // token claims user2 is accessing it + } as z.infer<typeof zAssetSignedTokenSchema>, + Date.now() + 60000, + ); + + // User2 attempts to access assetId (owned by User1) using a token that has User2's ID in its payload. + // The API route will use userIdUser2 from the token to query the DB for assetId. + // Since assetId is not owned by userIdUser2, the DB query will find nothing. + const res = await fetch( + `http://localhost:${port}/api/public/assets/${assetId}?token=${tokenForUser2AttemptingAsset1}`, + ); + expect(res.status).toBe(404); + expect(await res.json()).toEqual( + expect.objectContaining({ error: "Asset not found" }), + ); + }); + + it("should fail for a token referencing a non-existent assetId (expect 404)", async () => { + const nonExistentAssetId = `nonexistent-asset-${Date.now()}`; + const token = createSignedToken( + { + assetId: nonExistentAssetId, + userId, // Valid userId from the primary user + } as z.infer<typeof zAssetSignedTokenSchema>, + Date.now() + 60000, + ); + + const res = await fetch( + `http://localhost:${port}/api/public/assets/${nonExistentAssetId}?token=${token}`, + ); + expect(res.status).toBe(404); + expect(await res.json()).toEqual( + expect.objectContaining({ error: "Asset not found" }), + ); + }); + }); +}); diff --git a/packages/e2e_tests/tests/api/rss.test.ts b/packages/e2e_tests/tests/api/rss.test.ts new file mode 100644 index 00000000..8a7447e6 --- /dev/null +++ b/packages/e2e_tests/tests/api/rss.test.ts @@ -0,0 +1,155 @@ +import { beforeEach, describe, expect, inject, it } from "vitest"; + +import { BookmarkTypes } from "../../../shared/types/bookmarks"; +import { createTestUser } from "../../utils/api"; +import { getTrpcClient } from "../../utils/trpc"; + +describe("RSS Feed API", () => { + const port = inject("karakeepPort"); + + if (!port) { + throw new Error("Missing required environment variables"); + } + + async function fetchRssFeed(listId: string, token: string) { + return await fetch( + `http://localhost:${port}/api/v1/rss/lists/${listId}?token=${token}`, + ); + } + + async function seedDatabase() { + const trpcClient = getTrpcClient(apiKey); + + // Create two lists + const manualList = await trpcClient.lists.create.mutate({ + name: "Test List #1", + icon: "🚀", + type: "manual", + }); + + const smartList = await trpcClient.lists.create.mutate({ + name: "Test List #2", + icon: "🚀", + type: "smart", + query: "is:fav", + }); + + // Create two bookmarks + const createBookmark1 = await trpcClient.bookmarks.createBookmark.mutate({ + title: "Test Bookmark #1", + url: "https://example.com", + type: BookmarkTypes.LINK, + }); + + const createBookmark2 = await trpcClient.bookmarks.createBookmark.mutate({ + title: "Test Bookmark #2", + url: "https://example.com/2", + type: BookmarkTypes.LINK, + favourited: true, + }); + + await trpcClient.lists.addToList.mutate({ + listId: manualList.id, + bookmarkId: createBookmark1.id, + }); + + return { manualList, smartList, createBookmark1, createBookmark2 }; + } + + let apiKey: string; + + beforeEach(async () => { + apiKey = await createTestUser(); + }); + + it("should generate rss feed for manual lists", async () => { + const { manualList } = await seedDatabase(); + const trpcClient = getTrpcClient(apiKey); + + // Enable rss feed + const token = await trpcClient.lists.regenRssToken.mutate({ + listId: manualList.id, + }); + + const res = await fetchRssFeed(manualList.id, token.token); + expect(res.status).toBe(200); + expect(res.headers.get("Content-Type")).toBe("application/rss+xml"); + + const text = await res.text(); + expect(text).toContain("Test Bookmark #1"); + expect(text).not.toContain("Test Bookmark #2"); + }); + + it("should generate rss feed for smart lists", async () => { + const { smartList } = await seedDatabase(); + const trpcClient = getTrpcClient(apiKey); + + // Enable rss feed + const token = await trpcClient.lists.regenRssToken.mutate({ + listId: smartList.id, + }); + + const res = await fetchRssFeed(smartList.id, token.token); + expect(res.status).toBe(200); + expect(res.headers.get("Content-Type")).toBe("application/rss+xml"); + + const text = await res.text(); + expect(text).not.toContain("Test Bookmark #1"); + expect(text).toContain("Test Bookmark #2"); + }); + + it("should fail when the token is invalid", async () => { + const { smartList } = await seedDatabase(); + const trpcClient = getTrpcClient(apiKey); + + // Enable rss feed + const token = await trpcClient.lists.regenRssToken.mutate({ + listId: smartList.id, + }); + + let res = await fetchRssFeed(smartList.id, token.token); + expect(res.status).toBe(200); + + // Invalidate the token + await trpcClient.lists.regenRssToken.mutate({ + listId: smartList.id, + }); + + res = await fetchRssFeed(smartList.id, token.token); + expect(res.status).toBe(404); + }); + + it("should fail when rss gets disabled", async () => { + const { smartList } = await seedDatabase(); + const trpcClient = getTrpcClient(apiKey); + + // Enable rss feed + const token = await trpcClient.lists.regenRssToken.mutate({ + listId: smartList.id, + }); + + const res = await fetchRssFeed(smartList.id, token.token); + expect(res.status).toBe(200); + + // Disable rss feed + await trpcClient.lists.clearRssToken.mutate({ + listId: smartList.id, + }); + + const res2 = await fetchRssFeed(smartList.id, token.token); + expect(res2.status).toBe(404); + }); + + it("should fail when no token is provided", async () => { + const { smartList } = await seedDatabase(); + const trpcClient = getTrpcClient(apiKey); + + // Enable rss feed + await trpcClient.lists.regenRssToken.mutate({ + listId: smartList.id, + }); + + const res2 = await fetchRssFeed(smartList.id, ""); + expect(res2.status).toBe(400); + }); +}); diff --git a/packages/e2e_tests/tests/api/tags.test.ts b/packages/e2e_tests/tests/api/tags.test.ts index 3e3cacc0..6c387628 100644 --- a/packages/e2e_tests/tests/api/tags.test.ts +++ b/packages/e2e_tests/tests/api/tags.test.ts @@ -26,31 +26,16 @@ describe("Tags API", () => { }); it("should get, update and delete a tag", async () => { - // Create a bookmark first - const { data: createdBookmark } = await client.POST("/bookmarks", { + // Create a tag by attaching it to the bookmark + const { data: tag } = await client.POST("/tags", { body: { - type: "text", - title: "Test Bookmark", - text: "This is a test bookmark", + name: "Test Tag", }, }); + expect(tag).toBeDefined(); + expect(tag!.name).toBe("Test Tag"); - // Create a tag by attaching it to the bookmark - const { data: addTagResponse } = await client.POST( - "/bookmarks/{bookmarkId}/tags", - { - params: { - path: { - bookmarkId: createdBookmark!.id, - }, - }, - body: { - tags: [{ tagName: "Test Tag" }], - }, - }, - ); - - const tagId = addTagResponse!.attached[0]; + const tagId = tag!.id; // Get the tag const { data: retrievedTag, response: getResponse } = await client.GET( |
