1
23import { test, expect } from "../fixtures";
24import { createPage } from "../utils/api";
25import { waitForCondition } from "../utils/wait";
26
27interface ActivityItem {
28 id: string;
29 action: string;
30 resourceType: string;
31 resourceId: string;
32 workspaceId: string;
33 pageId: string | null;
34 actorName?: string | null;
35}
36
37
39async function userFeedHasPage(
40 api: { get: (u: string) => Promise<{ json: () => Promise<unknown> }> },
41 pageId: string,
42): Promise<boolean> {
43 const res = await api.get("/api/v1/users/me/activity");
44 const body = (await res.json()) as { data: ActivityItem[] };
45 return (body.data ?? []).some((r) => r.resourceType === "page" && r.resourceId === pageId);
46}
47
48async function spaceFeedHasPage(
49 api: { get: (u: string) => Promise<{ json: () => Promise<unknown> }> },
50 workspaceId: string,
51 pageId: string,
52): Promise<boolean> {
53 const res = await api.get(`/api/v1/workspaces/${workspaceId}/activity`);
54 const body = (await res.json()) as { data: ActivityItem[] };
55 return (body.data ?? []).some((r) => r.resourceType === "page" && r.resourceId === pageId);
56}
57
58test.describe("Confluence P2 — activity feeds (T-158)", () => {
59 test("user feed: a created page lands as a row and deep-links to it", async ({
60 authPage,
61 api,
62 freshWorkspace,
63 }) => {
64 const ws = await freshWorkspace("Activity Feed Space");
65 const page = await createPage(api, ws.id, { title: "Activity Probe Plan" });
66
67
68 await waitForCondition(
69 authPage,
70 () => userFeedHasPage(api, page.id),
71 { timeoutMs: 10_000, message: "page.created row did not land in the user feed" },
72 );
73
74 await authPage.goto("/activity");
75
76 const target = authPage
77 .locator(`[data-testid="activity-item"][href="/w/${ws.id}/page/${page.id}"]`);
78 await expect(target, "created-page row with deep link").toBeVisible({ timeout: 10_000 });
79
80
81 await expect(target.locator('[data-testid="activity-actor"]')).toHaveText("Demo User");
82 await expect(target).toContainText("created a page");
83
84
85 await target.click();
86 await authPage.waitForURL(/\/page\
87 expect(authPage.url()).toContain(`/page/${page.id}`);
88 });
89
90 test("space feed: the row appears on the workspace activity page and navigates", async ({
91 authPage,
92 api,
93 freshWorkspace,
94 }) => {
95 const ws = await freshWorkspace("Space Activity Space");
96 const page = await createPage(api, ws.id, { title: "Space Activity Probe" });
97
98 await waitForCondition(
99 authPage,
100 () => spaceFeedHasPage(api, ws.id, page.id),
101 { timeoutMs: 10_000, message: "page.created row did not land in the space feed" },
102 );
103
104 await authPage.goto(`/w/${ws.id}/activity`);
105 const target = authPage
106 .locator(`[data-testid="activity-item"][href="/w/${ws.id}/page/${page.id}"]`);
107 await expect(target, "created-page row on the space feed").toBeVisible({ timeout: 10_000 });
108 await expect(target.locator('[data-testid="activity-actor"]')).toHaveText("Demo User");
109 await expect(target).toContainText("created a page");
110
111 await target.click();
112 await authPage.waitForURL(/\/page\
113 expect(authPage.url()).toContain(`/page/${page.id}`);
114 });
115
116 test("no-leak: a non-member gets 403 (API) and is bounced off the route (web)", async ({
117 authPage,
118 api,
119 secondUser,
120 freshWorkspace,
121 }) => {
122 const ws = await freshWorkspace("Gated Activity Space");
123 const page = await createPage(api, ws.id, { title: "Gated Probe" });
124 await waitForCondition(authPage, () => spaceFeedHasPage(api, ws.id, page.id), {
125 timeoutMs: 10_000
126 });
127
128
129
130
131 const { page: secondPage, api: secondApi } = await secondUser();
132 const mineBody = (await (
133 await secondApi.get("/api/v1/workspaces")
134 ).json()) as { data?: Array<{ id: string }> };
135 const mineList = mineBody.data ?? [];
136 expect(mineList.length, "second user has a home workspace").toBeGreaterThan(0);
137 const mine = mineList[0]!;
138
139
140 const res = await secondApi.get(`/api/v1/workspaces/${ws.id}/activity`);
141 expect(res.status()).toBe(403);
142
143
144
145 await secondPage.goto(`/w/${ws.id}/activity`);
146 await secondPage.waitForURL(`**/w/${mine?.id}**`, { timeout: 15_000 });
147 await expect(secondPage.locator('[data-testid="activity-item"]')).toHaveCount(0);
148 });
149});
150