aboutsummaryrefslogtreecommitdiffstats
path: root/packages/e2e_tests/tests/api
diff options
context:
space:
mode:
Diffstat (limited to 'packages/e2e_tests/tests/api')
-rw-r--r--packages/e2e_tests/tests/api/assets.test.ts134
-rw-r--r--packages/e2e_tests/tests/api/bookmarks.test.ts288
-rw-r--r--packages/e2e_tests/tests/api/highlights.test.ts242
-rw-r--r--packages/e2e_tests/tests/api/lists.test.ts185
-rw-r--r--packages/e2e_tests/tests/api/tags.test.ts215
5 files changed, 1064 insertions, 0 deletions
diff --git a/packages/e2e_tests/tests/api/assets.test.ts b/packages/e2e_tests/tests/api/assets.test.ts
new file mode 100644
index 00000000..0fab3d3f
--- /dev/null
+++ b/packages/e2e_tests/tests/api/assets.test.ts
@@ -0,0 +1,134 @@
+import { createHoarderClient } from "@hoarderapp/sdk";
+import { assert, beforeEach, describe, expect, inject, it } from "vitest";
+
+import { createTestUser, uploadTestAsset } from "../../utils/api";
+
+describe("Assets API", () => {
+ const port = inject("hoarderPort");
+
+ if (!port) {
+ throw new Error("Missing required environment variables");
+ }
+
+ let client: ReturnType<typeof createHoarderClient>;
+ let apiKey: string;
+
+ beforeEach(async () => {
+ apiKey = await createTestUser();
+ client = createHoarderClient({
+ baseUrl: `http://localhost:${port}/api/v1/`,
+ headers: {
+ "Content-Type": "application/json",
+ authorization: `Bearer ${apiKey}`,
+ },
+ });
+ });
+
+ it("should upload and retrieve an asset", async () => {
+ // Create a test file
+ const file = new File(["test content"], "test.pdf", {
+ type: "application/pdf",
+ });
+
+ // Upload the asset
+ const uploadResponse = await uploadTestAsset(apiKey, port, file);
+ expect(uploadResponse.assetId).toBeDefined();
+ expect(uploadResponse.contentType).toBe("application/pdf");
+ expect(uploadResponse.fileName).toBe("test.pdf");
+
+ // Retrieve the asset
+ const resp = await fetch(
+ `http://localhost:${port}/api/assets/${uploadResponse.assetId}`,
+ {
+ headers: {
+ authorization: `Bearer ${apiKey}`,
+ },
+ },
+ );
+
+ expect(resp.status).toBe(200);
+ });
+
+ it("should attach an asset to a bookmark", async () => {
+ // Create a test file
+ const file = new File(["test content"], "test.pdf", {
+ type: "application/pdf",
+ });
+
+ // Upload the asset
+ const uploadResponse = await uploadTestAsset(apiKey, port, file);
+
+ // Create a bookmark
+ const { data: createdBookmark } = await client.POST("/bookmarks", {
+ body: {
+ type: "asset",
+ title: "Test Asset Bookmark",
+ assetType: "pdf",
+ assetId: uploadResponse.assetId,
+ },
+ });
+
+ expect(createdBookmark).toBeDefined();
+ expect(createdBookmark?.id).toBeDefined();
+
+ // Get the bookmark and verify asset
+ const { data: retrievedBookmark } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: {
+ path: {
+ bookmarkId: createdBookmark!.id,
+ },
+ },
+ },
+ );
+
+ expect(retrievedBookmark).toBeDefined();
+ assert(retrievedBookmark!.content.type === "asset");
+ expect(retrievedBookmark!.content.assetId).toBe(uploadResponse.assetId);
+ });
+
+ it("should delete asset when deleting bookmark", async () => {
+ // Create a test file
+ const file = new File(["test content"], "test.pdf", {
+ type: "application/pdf",
+ });
+
+ // Upload the asset
+ const uploadResponse = await uploadTestAsset(apiKey, port, file);
+
+ // Create a bookmark
+ const { data: createdBookmark } = await client.POST("/bookmarks", {
+ body: {
+ type: "asset",
+ title: "Test Asset Bookmark",
+ assetType: "pdf",
+ assetId: uploadResponse.assetId,
+ },
+ });
+
+ // Delete the bookmark
+ const { response: deleteResponse } = await client.DELETE(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: {
+ path: {
+ bookmarkId: createdBookmark!.id,
+ },
+ },
+ },
+ );
+ expect(deleteResponse.status).toBe(204);
+
+ // Verify asset is deleted
+ const assetResponse = await fetch(
+ `http://localhost:${port}/api/assets/${uploadResponse.assetId}`,
+ {
+ headers: {
+ authorization: `Bearer ${apiKey}`,
+ },
+ },
+ );
+ expect(assetResponse.status).toBe(404);
+ });
+});
diff --git a/packages/e2e_tests/tests/api/bookmarks.test.ts b/packages/e2e_tests/tests/api/bookmarks.test.ts
new file mode 100644
index 00000000..727ca758
--- /dev/null
+++ b/packages/e2e_tests/tests/api/bookmarks.test.ts
@@ -0,0 +1,288 @@
+import { createHoarderClient } from "@hoarderapp/sdk";
+import { assert, beforeEach, describe, expect, inject, it } from "vitest";
+
+import { createTestUser } from "../../utils/api";
+
+describe("Bookmarks API", () => {
+ const port = inject("hoarderPort");
+
+ if (!port) {
+ throw new Error("Missing required environment variables");
+ }
+
+ let client: ReturnType<typeof createHoarderClient>;
+ let apiKey: string;
+
+ beforeEach(async () => {
+ apiKey = await createTestUser();
+ client = createHoarderClient({
+ baseUrl: `http://localhost:${port}/api/v1/`,
+ headers: {
+ "Content-Type": "application/json",
+ authorization: `Bearer ${apiKey}`,
+ },
+ });
+ });
+
+ it("should create and retrieve a bookmark", async () => {
+ // Create a new bookmark
+ const {
+ data: createdBookmark,
+ response: createResponse,
+ error,
+ } = await client.POST("/bookmarks", {
+ body: {
+ type: "text",
+ title: "Test Bookmark",
+ text: "This is a test bookmark",
+ },
+ });
+
+ if (error) {
+ console.error("Error creating bookmark:", error);
+ }
+
+ expect(createResponse.status).toBe(201);
+ expect(createdBookmark).toBeDefined();
+ expect(createdBookmark?.id).toBeDefined();
+
+ // Get the created bookmark
+ const { data: retrievedBookmark, response: getResponse } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: {
+ path: {
+ bookmarkId: createdBookmark.id,
+ },
+ },
+ },
+ );
+
+ expect(getResponse.status).toBe(200);
+ expect(retrievedBookmark!.id).toBe(createdBookmark.id);
+ expect(retrievedBookmark!.title).toBe("Test Bookmark");
+ assert(retrievedBookmark!.content.type === "text");
+ expect(retrievedBookmark!.content.text).toBe("This is a test bookmark");
+ });
+
+ it("should update a bookmark", async () => {
+ // Create a new bookmark
+ const { data: createdBookmark, error: createError } = await client.POST(
+ "/bookmarks",
+ {
+ body: {
+ type: "text",
+ title: "Test Bookmark",
+ text: "This is a test bookmark",
+ },
+ },
+ );
+
+ if (createError) {
+ console.error("Error creating bookmark:", createError);
+ throw createError;
+ }
+ if (!createdBookmark) {
+ throw new Error("Bookmark creation failed");
+ }
+
+ // Update the bookmark
+ const { data: updatedBookmark, response: updateResponse } =
+ await client.PATCH("/bookmarks/{bookmarkId}", {
+ params: {
+ path: {
+ bookmarkId: createdBookmark.id,
+ },
+ },
+ body: {
+ title: "Updated Title",
+ },
+ });
+
+ expect(updateResponse.status).toBe(200);
+ expect(updatedBookmark!.title).toBe("Updated Title");
+ });
+
+ it("should delete a bookmark", async () => {
+ // Create a new bookmark
+ const { data: createdBookmark, error: createError } = await client.POST(
+ "/bookmarks",
+ {
+ body: {
+ type: "text",
+ title: "Test Bookmark",
+ text: "This is a test bookmark",
+ },
+ },
+ );
+
+ if (createError) {
+ console.error("Error creating bookmark:", createError);
+ throw createError;
+ }
+ if (!createdBookmark) {
+ throw new Error("Bookmark creation failed");
+ }
+
+ // Delete the bookmark
+ const { response: deleteResponse } = await client.DELETE(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: {
+ path: {
+ bookmarkId: createdBookmark.id,
+ },
+ },
+ },
+ );
+
+ expect(deleteResponse.status).toBe(204);
+
+ // Verify it's deleted
+ const { response: getResponse } = await client.GET(
+ "/bookmarks/{bookmarkId}",
+ {
+ params: {
+ path: {
+ bookmarkId: createdBookmark.id,
+ },
+ },
+ },
+ );
+
+ expect(getResponse.status).toBe(404);
+ });
+
+ it("should paginate through bookmarks", async () => {
+ // Create multiple bookmarks
+ const bookmarkPromises = Array.from({ length: 5 }, (_, i) =>
+ client.POST("/bookmarks", {
+ body: {
+ type: "text",
+ title: `Test Bookmark ${i}`,
+ text: `This is test bookmark ${i}`,
+ },
+ }),
+ );
+
+ const createdBookmarks = await Promise.all(bookmarkPromises);
+ const bookmarkIds = createdBookmarks.map((b) => b.data!.id);
+
+ // Get first page
+ const { data: firstPage, response: firstResponse } = await client.GET(
+ "/bookmarks",
+ {
+ params: {
+ query: {
+ limit: 2,
+ },
+ },
+ },
+ );
+
+ expect(firstResponse.status).toBe(200);
+ expect(firstPage!.bookmarks.length).toBe(2);
+ expect(firstPage!.nextCursor).toBeDefined();
+
+ // Get second page
+ const { data: secondPage, response: secondResponse } = await client.GET(
+ "/bookmarks",
+ {
+ params: {
+ query: {
+ limit: 2,
+ cursor: firstPage!.nextCursor!,
+ },
+ },
+ },
+ );
+
+ expect(secondResponse.status).toBe(200);
+ expect(secondPage!.bookmarks.length).toBe(2);
+ expect(secondPage!.nextCursor).toBeDefined();
+
+ // Get final page
+ const { data: finalPage, response: finalResponse } = await client.GET(
+ "/bookmarks",
+ {
+ params: {
+ query: {
+ limit: 2,
+ cursor: secondPage!.nextCursor!,
+ },
+ },
+ },
+ );
+
+ expect(finalResponse.status).toBe(200);
+ expect(finalPage!.bookmarks.length).toBe(1);
+ expect(finalPage!.nextCursor).toBeNull();
+
+ // Verify all bookmarks were returned
+ const allBookmarks = [
+ ...firstPage!.bookmarks,
+ ...secondPage!.bookmarks,
+ ...finalPage!.bookmarks,
+ ];
+ expect(allBookmarks.map((b) => b.id)).toEqual(
+ expect.arrayContaining(bookmarkIds),
+ );
+ });
+
+ it("should manage tags on a bookmark", async () => {
+ // Create a new bookmark
+ const { data: createdBookmark, error: createError } = await client.POST(
+ "/bookmarks",
+ {
+ body: {
+ type: "text",
+ title: "Test Bookmark",
+ text: "This is a test bookmark",
+ },
+ },
+ );
+
+ if (createError) {
+ console.error("Error creating bookmark:", createError);
+ throw createError;
+ }
+ if (!createdBookmark) {
+ throw new Error("Bookmark creation failed");
+ }
+
+ // Add tags
+ const { data: addTagsResponse, response: addTagsRes } = await client.POST(
+ "/bookmarks/{bookmarkId}/tags",
+ {
+ params: {
+ path: {
+ bookmarkId: createdBookmark.id,
+ },
+ },
+ body: {
+ tags: [{ tagName: "test-tag" }],
+ },
+ },
+ );
+
+ expect(addTagsRes.status).toBe(200);
+ expect(addTagsResponse!.attached.length).toBe(1);
+
+ // Remove tags
+ const { response: removeTagsRes } = await client.DELETE(
+ "/bookmarks/{bookmarkId}/tags",
+ {
+ params: {
+ path: {
+ bookmarkId: createdBookmark.id,
+ },
+ },
+ body: {
+ tags: [{ tagId: addTagsResponse!.attached[0] }],
+ },
+ },
+ );
+
+ expect(removeTagsRes.status).toBe(200);
+ });
+});
diff --git a/packages/e2e_tests/tests/api/highlights.test.ts b/packages/e2e_tests/tests/api/highlights.test.ts
new file mode 100644
index 00000000..94a4d28b
--- /dev/null
+++ b/packages/e2e_tests/tests/api/highlights.test.ts
@@ -0,0 +1,242 @@
+import { createHoarderClient } from "@hoarderapp/sdk";
+import { beforeEach, describe, expect, inject, it } from "vitest";
+
+import { createTestUser } from "../../utils/api";
+
+describe("Highlights API", () => {
+ const port = inject("hoarderPort");
+
+ if (!port) {
+ throw new Error("Missing required environment variables");
+ }
+
+ let client: ReturnType<typeof createHoarderClient>;
+ let apiKey: string;
+
+ beforeEach(async () => {
+ apiKey = await createTestUser();
+ client = createHoarderClient({
+ baseUrl: `http://localhost:${port}/api/v1/`,
+ headers: {
+ "Content-Type": "application/json",
+ authorization: `Bearer ${apiKey}`,
+ },
+ });
+ });
+
+ it("should create, get, update and delete a highlight", async () => {
+ // Create a bookmark first
+ const { data: createdBookmark } = await client.POST("/bookmarks", {
+ body: {
+ type: "text",
+ title: "Test Bookmark",
+ text: "This is a test bookmark",
+ },
+ });
+
+ // Create a new highlight
+ const { data: createdHighlight, response: createResponse } =
+ await client.POST("/highlights", {
+ body: {
+ bookmarkId: createdBookmark!.id,
+ startOffset: 0,
+ endOffset: 5,
+ text: "This ",
+ note: "Test note",
+ color: "yellow",
+ },
+ });
+
+ expect(createResponse.status).toBe(201);
+ expect(createdHighlight).toBeDefined();
+ expect(createdHighlight?.id).toBeDefined();
+ expect(createdHighlight?.text).toBe("This ");
+ expect(createdHighlight?.note).toBe("Test note");
+
+ // Get the created highlight
+ const { data: retrievedHighlight, response: getResponse } =
+ await client.GET("/highlights/{highlightId}", {
+ params: {
+ path: {
+ highlightId: createdHighlight!.id,
+ },
+ },
+ });
+
+ expect(getResponse.status).toBe(200);
+ expect(retrievedHighlight!.id).toBe(createdHighlight!.id);
+ expect(retrievedHighlight!.text).toBe("This ");
+ expect(retrievedHighlight!.note).toBe("Test note");
+
+ // Update the highlight
+ const { data: updatedHighlight, response: updateResponse } =
+ await client.PATCH("/highlights/{highlightId}", {
+ params: {
+ path: {
+ highlightId: createdHighlight!.id,
+ },
+ },
+ body: {
+ color: "blue",
+ },
+ });
+
+ expect(updateResponse.status).toBe(200);
+ expect(updatedHighlight!.color).toBe("blue");
+
+ // Delete the highlight
+ const { response: deleteResponse } = await client.DELETE(
+ "/highlights/{highlightId}",
+ {
+ params: {
+ path: {
+ highlightId: createdHighlight!.id,
+ },
+ },
+ },
+ );
+
+ expect(deleteResponse.status).toBe(200);
+
+ // Verify it's deleted
+ const { response: getDeletedResponse } = await client.GET(
+ "/highlights/{highlightId}",
+ {
+ params: {
+ path: {
+ highlightId: createdHighlight!.id,
+ },
+ },
+ },
+ );
+
+ expect(getDeletedResponse.status).toBe(404);
+ });
+
+ it("should paginate through highlights", async () => {
+ // Create a bookmark first
+ const { data: createdBookmark } = await client.POST("/bookmarks", {
+ body: {
+ type: "text",
+ title: "Test Bookmark",
+ text: "This is a test bookmark",
+ },
+ });
+
+ // Create multiple highlights
+ const highlightPromises = Array.from({ length: 5 }, (_, i) =>
+ client.POST("/highlights", {
+ body: {
+ bookmarkId: createdBookmark!.id,
+ startOffset: i * 5,
+ endOffset: (i + 1) * 5,
+ text: `Highlight ${i}`,
+ note: `Note ${i}`,
+ },
+ }),
+ );
+
+ await Promise.all(highlightPromises);
+
+ // Get first page
+ const { data: firstPage, response: firstResponse } = await client.GET(
+ "/highlights",
+ {
+ params: {
+ query: {
+ limit: 2,
+ },
+ },
+ },
+ );
+
+ expect(firstResponse.status).toBe(200);
+ expect(firstPage!.highlights.length).toBe(2);
+ expect(firstPage!.nextCursor).toBeDefined();
+
+ // Get second page
+ const { data: secondPage, response: secondResponse } = await client.GET(
+ "/highlights",
+ {
+ params: {
+ query: {
+ limit: 2,
+ cursor: firstPage!.nextCursor!,
+ },
+ },
+ },
+ );
+
+ expect(secondResponse.status).toBe(200);
+ expect(secondPage!.highlights.length).toBe(2);
+ expect(secondPage!.nextCursor).toBeDefined();
+
+ // Get final page
+ const { data: finalPage, response: finalResponse } = await client.GET(
+ "/highlights",
+ {
+ params: {
+ query: {
+ limit: 2,
+ cursor: secondPage!.nextCursor!,
+ },
+ },
+ },
+ );
+
+ expect(finalResponse.status).toBe(200);
+ expect(finalPage!.highlights.length).toBe(1);
+ expect(finalPage!.nextCursor).toBeNull();
+ });
+
+ it("should get highlights for a bookmark", async () => {
+ // Create a bookmark first
+ const { data: createdBookmark } = await client.POST("/bookmarks", {
+ body: {
+ type: "text",
+ title: "Test Bookmark",
+ text: "This is a test bookmark",
+ },
+ });
+
+ // Create highlights
+ await client.POST("/highlights", {
+ body: {
+ bookmarkId: createdBookmark!.id,
+ startOffset: 0,
+ endOffset: 5,
+ text: "This ",
+ note: "First highlight",
+ color: "yellow",
+ },
+ });
+
+ await client.POST("/highlights", {
+ body: {
+ bookmarkId: createdBookmark!.id,
+ startOffset: 5,
+ endOffset: 10,
+ text: "is a ",
+ note: "Second highlight",
+ color: "blue",
+ },
+ });
+
+ // Get highlights for bookmark
+ const { data: highlights, response: getResponse } = await client.GET(
+ "/bookmarks/{bookmarkId}/highlights",
+ {
+ params: {
+ path: {
+ bookmarkId: createdBookmark!.id,
+ },
+ },
+ },
+ );
+
+ expect(getResponse.status).toBe(200);
+ expect(highlights!.highlights.length).toBe(2);
+ expect(highlights!.highlights.map((h) => h.text)).toContain("This ");
+ expect(highlights!.highlights.map((h) => h.text)).toContain("is a ");
+ });
+});
diff --git a/packages/e2e_tests/tests/api/lists.test.ts b/packages/e2e_tests/tests/api/lists.test.ts
new file mode 100644
index 00000000..2a954b6f
--- /dev/null
+++ b/packages/e2e_tests/tests/api/lists.test.ts
@@ -0,0 +1,185 @@
+import { createHoarderClient } from "@hoarderapp/sdk";
+import { beforeEach, describe, expect, inject, it } from "vitest";
+
+import { createTestUser } from "../../utils/api";
+
+describe("Lists API", () => {
+ const port = inject("hoarderPort");
+
+ if (!port) {
+ throw new Error("Missing required environment variables");
+ }
+
+ let client: ReturnType<typeof createHoarderClient>;
+ let apiKey: string;
+
+ beforeEach(async () => {
+ apiKey = await createTestUser();
+ client = createHoarderClient({
+ baseUrl: `http://localhost:${port}/api/v1/`,
+ headers: {
+ "Content-Type": "application/json",
+ authorization: `Bearer ${apiKey}`,
+ },
+ });
+ });
+
+ it("should create, get, update and delete a list", async () => {
+ // Create a new list
+ const { data: createdList, response: createResponse } = await client.POST(
+ "/lists",
+ {
+ body: {
+ name: "Test List",
+ icon: "🚀",
+ },
+ },
+ );
+
+ expect(createResponse.status).toBe(201);
+ expect(createdList).toBeDefined();
+ expect(createdList?.id).toBeDefined();
+ expect(createdList?.name).toBe("Test List");
+
+ // Get the created list
+ const { data: retrievedList, response: getResponse } = await client.GET(
+ "/lists/{listId}",
+ {
+ params: {
+ path: {
+ listId: createdList!.id,
+ },
+ },
+ },
+ );
+
+ expect(getResponse.status).toBe(200);
+ expect(retrievedList!.id).toBe(createdList!.id);
+ expect(retrievedList!.name).toBe("Test List");
+
+ // Update the list
+ const { data: updatedList, response: updateResponse } = await client.PATCH(
+ "/lists/{listId}",
+ {
+ params: {
+ path: {
+ listId: createdList!.id,
+ },
+ },
+ body: {
+ name: "Updated List",
+ },
+ },
+ );
+
+ expect(updateResponse.status).toBe(200);
+ expect(updatedList!.name).toBe("Updated List");
+
+ // Delete the list
+ const { response: deleteResponse } = await client.DELETE(
+ "/lists/{listId}",
+ {
+ params: {
+ path: {
+ listId: createdList!.id,
+ },
+ },
+ },
+ );
+
+ expect(deleteResponse.status).toBe(204);
+
+ // Verify it's deleted
+ const { response: getDeletedResponse } = await client.GET(
+ "/lists/{listId}",
+ {
+ params: {
+ path: {
+ listId: createdList!.id,
+ },
+ },
+ },
+ );
+
+ expect(getDeletedResponse.status).toBe(404);
+ });
+
+ it("should manage bookmarks in a list", async () => {
+ // Create a list
+ const { data: createdList } = await client.POST("/lists", {
+ body: {
+ name: "Test List",
+ icon: "🚀",
+ },
+ });
+
+ // Create a bookmark
+ const { data: createdBookmark } = await client.POST("/bookmarks", {
+ body: {
+ type: "text",
+ title: "Test Bookmark",
+ text: "This is a test bookmark",
+ },
+ });
+
+ // Add bookmark to list
+ const { response: addResponse } = await client.PUT(
+ "/lists/{listId}/bookmarks/{bookmarkId}",
+ {
+ params: {
+ path: {
+ listId: createdList!.id,
+ bookmarkId: createdBookmark!.id,
+ },
+ },
+ },
+ );
+
+ expect(addResponse.status).toBe(204);
+
+ // Get bookmarks in list
+ const { data: listBookmarks, response: getResponse } = await client.GET(
+ "/lists/{listId}/bookmarks",
+ {
+ params: {
+ path: {
+ listId: createdList!.id,
+ },
+ },
+ },
+ );
+
+ expect(getResponse.status).toBe(200);
+ expect(listBookmarks!.bookmarks.length).toBe(1);
+ expect(listBookmarks!.bookmarks[0].id).toBe(createdBookmark!.id);
+
+ // Remove bookmark from list
+ const { response: removeResponse } = await client.DELETE(
+ "/lists/{listId}/bookmarks/{bookmarkId}",
+ {
+ params: {
+ path: {
+ listId: createdList!.id,
+ bookmarkId: createdBookmark!.id,
+ },
+ },
+ },
+ );
+
+ expect(removeResponse.status).toBe(204);
+
+ // Verify bookmark is removed
+ const { data: updatedListBookmarks } = await client.GET(
+ "/lists/{listId}/bookmarks",
+ {
+ params: {
+ path: {
+ listId: createdList!.id,
+ },
+ },
+ },
+ );
+
+ expect(updatedListBookmarks!.bookmarks.length).toBe(0);
+ });
+});
diff --git a/packages/e2e_tests/tests/api/tags.test.ts b/packages/e2e_tests/tests/api/tags.test.ts
new file mode 100644
index 00000000..dfd3f14b
--- /dev/null
+++ b/packages/e2e_tests/tests/api/tags.test.ts
@@ -0,0 +1,215 @@
+import { createHoarderClient } from "@hoarderapp/sdk";
+import { beforeEach, describe, expect, inject, it } from "vitest";
+
+import { createTestUser } from "../../utils/api";
+
+describe("Tags API", () => {
+ const port = inject("hoarderPort");
+
+ if (!port) {
+ throw new Error("Missing required environment variables");
+ }
+
+ let client: ReturnType<typeof createHoarderClient>;
+ let apiKey: string;
+
+ beforeEach(async () => {
+ apiKey = await createTestUser();
+ client = createHoarderClient({
+ baseUrl: `http://localhost:${port}/api/v1/`,
+ headers: {
+ "Content-Type": "application/json",
+ authorization: `Bearer ${apiKey}`,
+ },
+ });
+ });
+
+ it("should get, update and delete a tag", async () => {
+ // Create a bookmark first
+ const { data: createdBookmark } = await client.POST("/bookmarks", {
+ body: {
+ type: "text",
+ title: "Test Bookmark",
+ text: "This is a test bookmark",
+ },
+ });
+
+ // 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];
+
+ // Get the tag
+ const { data: retrievedTag, response: getResponse } = await client.GET(
+ "/tags/{tagId}",
+ {
+ params: {
+ path: {
+ tagId,
+ },
+ },
+ },
+ );
+
+ expect(getResponse.status).toBe(200);
+ expect(retrievedTag!.id).toBe(tagId);
+ expect(retrievedTag!.name).toBe("Test Tag");
+
+ // Update the tag
+ const { data: updatedTag, response: updateResponse } = await client.PATCH(
+ "/tags/{tagId}",
+ {
+ params: {
+ path: {
+ tagId,
+ },
+ },
+ body: {
+ name: "Updated Tag",
+ },
+ },
+ );
+
+ expect(updateResponse.status).toBe(200);
+ expect(updatedTag!.name).toBe("Updated Tag");
+
+ // Delete the tag
+ const { response: deleteResponse } = await client.DELETE("/tags/{tagId}", {
+ params: {
+ path: {
+ tagId,
+ },
+ },
+ });
+
+ expect(deleteResponse.status).toBe(204);
+
+ // Verify it's deleted
+ const { response: getDeletedResponse } = await client.GET("/tags/{tagId}", {
+ params: {
+ path: {
+ tagId,
+ },
+ },
+ });
+
+ expect(getDeletedResponse.status).toBe(404);
+ });
+
+ it("should manage bookmarks with a tag", async () => {
+ // Create a bookmark first
+ const { data: firstBookmark } = await client.POST("/bookmarks", {
+ body: {
+ type: "text",
+ title: "Test Bookmark",
+ text: "This is a test bookmark",
+ },
+ });
+
+ // Create a tag by attaching it to the bookmark
+ const { data: addTagResponse } = await client.POST(
+ "/bookmarks/{bookmarkId}/tags",
+ {
+ params: {
+ path: {
+ bookmarkId: firstBookmark!.id,
+ },
+ },
+ body: {
+ tags: [{ tagName: "Test Tag" }],
+ },
+ },
+ );
+
+ const tagId = addTagResponse!.attached[0];
+
+ // Add tag to another bookmark
+ const { data: secondBookmark } = await client.POST("/bookmarks", {
+ body: {
+ type: "text",
+ title: "Second Bookmark",
+ text: "This is another test bookmark",
+ },
+ });
+
+ const { data: addSecondTagResponse, response: addResponse } =
+ await client.POST("/bookmarks/{bookmarkId}/tags", {
+ params: {
+ path: {
+ bookmarkId: secondBookmark!.id,
+ },
+ },
+ body: {
+ tags: [{ tagId }],
+ },
+ });
+
+ expect(addResponse.status).toBe(200);
+ expect(addSecondTagResponse!.attached.length).toBe(1);
+
+ // Get bookmarks with tag
+ const { data: taggedBookmarks, response: getResponse } = await client.GET(
+ "/tags/{tagId}/bookmarks",
+ {
+ params: {
+ path: {
+ tagId,
+ },
+ },
+ },
+ );
+
+ expect(getResponse.status).toBe(200);
+ expect(taggedBookmarks!.bookmarks.length).toBe(2);
+ expect(taggedBookmarks!.bookmarks.map((b) => b.id)).toContain(
+ firstBookmark!.id,
+ );
+ expect(taggedBookmarks!.bookmarks.map((b) => b.id)).toContain(
+ secondBookmark!.id,
+ );
+
+ // Remove tag from first bookmark
+ const { response: removeResponse } = await client.DELETE(
+ "/bookmarks/{bookmarkId}/tags",
+ {
+ params: {
+ path: {
+ bookmarkId: firstBookmark!.id,
+ },
+ },
+ body: {
+ tags: [{ tagId }],
+ },
+ },
+ );
+
+ expect(removeResponse.status).toBe(200);
+
+ // Verify tag is still on second bookmark
+ const { data: updatedTaggedBookmarks } = await client.GET(
+ "/tags/{tagId}/bookmarks",
+ {
+ params: {
+ path: {
+ tagId,
+ },
+ },
+ },
+ );
+
+ expect(updatedTaggedBookmarks!.bookmarks.length).toBe(1);
+ expect(updatedTaggedBookmarks!.bookmarks[0].id).toBe(secondBookmark!.id);
+ });
+});