@firedrill-tools/linkedin 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +189 -0
  3. package/firedrill/agent.target.json +16 -0
  4. package/firedrill/baseline.scenario.json +1834 -0
  5. package/firedrill/comment-throttled.scenario.json +11 -0
  6. package/firedrill/conformance.suite.json +27 -0
  7. package/firedrill/linkedin-comment-throttled.drill.json +51 -0
  8. package/firedrill/linkedin-comments-connection.drill.json +43 -0
  9. package/firedrill/linkedin-comments.drill.json +196 -0
  10. package/firedrill/linkedin-denied.drill.json +383 -0
  11. package/firedrill/linkedin-fresh-install.drill.json +43 -0
  12. package/firedrill/linkedin-identity.drill.json +73 -0
  13. package/firedrill/linkedin-invalid-token.drill.json +393 -0
  14. package/firedrill/linkedin-limited-scopes.drill.json +88 -0
  15. package/firedrill/linkedin-openid-only.drill.json +383 -0
  16. package/firedrill/linkedin-orgs-connection.drill.json +28 -0
  17. package/firedrill/linkedin-orgs-network.drill.json +193 -0
  18. package/firedrill/linkedin-post-lost-response.drill.json +61 -0
  19. package/firedrill/linkedin-posts-lifecycle.drill.json +228 -0
  20. package/firedrill/linkedin-reactions-metadata.drill.json +196 -0
  21. package/firedrill/linkedin-reactions-outage.drill.json +66 -0
  22. package/firedrill/linkedin-tight-limits.drill.json +228 -0
  23. package/firedrill/linkedin-ugc-legacy.drill.json +43 -0
  24. package/firedrill/linkedin-visibility-connection.drill.json +73 -0
  25. package/firedrill/linkedin-visibility-stranger.drill.json +88 -0
  26. package/firedrill/linkedin-wire-errors.drill.json +353 -0
  27. package/firedrill/post-lost-response.scenario.json +11 -0
  28. package/firedrill/reactions-outage.scenario.json +11 -0
  29. package/firedrill/tight-limits.scenario.json +16 -0
  30. package/firedrill/tools/linkedin/app/assets/ATTRIBUTION.md +28 -0
  31. package/firedrill/tools/linkedin/app/assets/fonts/OFL.txt +93 -0
  32. package/firedrill/tools/linkedin/app/assets/linkedin-in.svg +1 -0
  33. package/firedrill/tools/linkedin/app/assets/linkedin-wordmark.svg +1 -0
  34. package/firedrill/tools/linkedin/app/assets/linkedin.svg +1 -0
  35. package/firedrill/tools/linkedin/app/site/app.js +80 -0
  36. package/firedrill/tools/linkedin/app/site/assets/fonts/fira-sans-latin-400-normal.woff2 +0 -0
  37. package/firedrill/tools/linkedin/app/site/assets/fonts/fira-sans-latin-600-normal.woff2 +0 -0
  38. package/firedrill/tools/linkedin/app/site/assets/fonts/fira-sans-latin-700-normal.woff2 +0 -0
  39. package/firedrill/tools/linkedin/app/site/assets/fonts/fira-sans-latin-ext-400-normal.woff2 +0 -0
  40. package/firedrill/tools/linkedin/app/site/assets/fonts/fira-sans-latin-ext-600-normal.woff2 +0 -0
  41. package/firedrill/tools/linkedin/app/site/assets/fonts/fira-sans-latin-ext-700-normal.woff2 +0 -0
  42. package/firedrill/tools/linkedin/app/site/assets/linkedin-wordmark.svg +1 -0
  43. package/firedrill/tools/linkedin/app/site/assets/linkedin.svg +1 -0
  44. package/firedrill/tools/linkedin/app/site/base.css +87 -0
  45. package/firedrill/tools/linkedin/app/site/comment-box.js +119 -0
  46. package/firedrill/tools/linkedin/app/site/comments.css +59 -0
  47. package/firedrill/tools/linkedin/app/site/comments.js +188 -0
  48. package/firedrill/tools/linkedin/app/site/company.js +115 -0
  49. package/firedrill/tools/linkedin/app/site/composer.js +112 -0
  50. package/firedrill/tools/linkedin/app/site/feed.css +105 -0
  51. package/firedrill/tools/linkedin/app/site/feed.js +134 -0
  52. package/firedrill/tools/linkedin/app/site/icons.js +70 -0
  53. package/firedrill/tools/linkedin/app/site/index.html +58 -0
  54. package/firedrill/tools/linkedin/app/site/nav.css +51 -0
  55. package/firedrill/tools/linkedin/app/site/network.js +77 -0
  56. package/firedrill/tools/linkedin/app/site/overlay.css +62 -0
  57. package/firedrill/tools/linkedin/app/site/overlay.js +108 -0
  58. package/firedrill/tools/linkedin/app/site/pages.css +78 -0
  59. package/firedrill/tools/linkedin/app/site/post-actions.js +83 -0
  60. package/firedrill/tools/linkedin/app/site/post-menu.js +72 -0
  61. package/firedrill/tools/linkedin/app/site/post.css +82 -0
  62. package/firedrill/tools/linkedin/app/site/post.js +131 -0
  63. package/firedrill/tools/linkedin/app/site/profile.js +139 -0
  64. package/firedrill/tools/linkedin/app/site/reactions.js +46 -0
  65. package/firedrill/tools/linkedin/app/site/reactors.js +71 -0
  66. package/firedrill/tools/linkedin/app/site/shell.js +107 -0
  67. package/firedrill/tools/linkedin/app/site/store.js +62 -0
  68. package/firedrill/tools/linkedin/app/site/text.js +80 -0
  69. package/firedrill/tools/linkedin/app/site/ui.js +142 -0
  70. package/firedrill/tools/linkedin/behavior.mjs +66 -0
  71. package/firedrill/tools/linkedin/lib/access.mjs +69 -0
  72. package/firedrill/tools/linkedin/lib/auth.mjs +115 -0
  73. package/firedrill/tools/linkedin/lib/cascade.mjs +28 -0
  74. package/firedrill/tools/linkedin/lib/errors.mjs +50 -0
  75. package/firedrill/tools/linkedin/lib/message.mjs +43 -0
  76. package/firedrill/tools/linkedin/lib/paging.mjs +54 -0
  77. package/firedrill/tools/linkedin/lib/postinput.mjs +47 -0
  78. package/firedrill/tools/linkedin/lib/render.mjs +95 -0
  79. package/firedrill/tools/linkedin/lib/routes.mjs +219 -0
  80. package/firedrill/tools/linkedin/lib/social.mjs +25 -0
  81. package/firedrill/tools/linkedin/lib/store.mjs +54 -0
  82. package/firedrill/tools/linkedin/lib/text.mjs +55 -0
  83. package/firedrill/tools/linkedin/lib/urn.mjs +93 -0
  84. package/firedrill/tools/linkedin/lib/wire.mjs +125 -0
  85. package/firedrill/tools/linkedin/linkedin.tool.json +8935 -0
  86. package/firedrill/tools/linkedin/ops/comments-read.mjs +57 -0
  87. package/firedrill/tools/linkedin/ops/comments-write.mjs +104 -0
  88. package/firedrill/tools/linkedin/ops/feed.mjs +74 -0
  89. package/firedrill/tools/linkedin/ops/network.mjs +46 -0
  90. package/firedrill/tools/linkedin/ops/orgs.mjs +93 -0
  91. package/firedrill/tools/linkedin/ops/posts-edit.mjs +71 -0
  92. package/firedrill/tools/linkedin/ops/posts-read.mjs +54 -0
  93. package/firedrill/tools/linkedin/ops/posts-write.mjs +74 -0
  94. package/firedrill/tools/linkedin/ops/profile.mjs +38 -0
  95. package/firedrill/tools/linkedin/ops/reactions.mjs +78 -0
  96. package/firedrill/tools/linkedin/ops/social.mjs +34 -0
  97. package/firedrill/world.json +2585 -0
  98. package/firedrill.json +5 -0
  99. package/package.json +52 -0
  100. package/starter.json +1833 -0
  101. package/test/client.mjs +71 -0
  102. package/test/conformance.mjs +20 -0
  103. package/test/fixtures.mjs +182 -0
  104. package/test/flows/comments.mjs +116 -0
  105. package/test/flows/every.mjs +38 -0
  106. package/test/flows/faults.mjs +56 -0
  107. package/test/flows/identity.mjs +70 -0
  108. package/test/flows/orgs.mjs +107 -0
  109. package/test/flows/posts.mjs +196 -0
  110. package/test/flows/reactions.mjs +101 -0
  111. package/test/flows/wire.mjs +62 -0
@@ -0,0 +1,107 @@
1
+ // Organization, page role, network size, connection and feed flows.
2
+ import assert from "node:assert/strict";
3
+ import { enc, get, op, orgUrn, personUrn } from "../client.mjs";
4
+ import { ORGS, PEOPLE, POSTS } from "../fixtures.mjs";
5
+
6
+ const maya = personUrn(PEOPLE.maya);
7
+
8
+ async function organizations() {
9
+ const org = (await get(`/rest/organizations/${ORGS.northwind}`)).body;
10
+ assert.equal(org.$URN, orgUrn(ORGS.northwind));
11
+ assert.equal(org.localizedName, "Northwind Analytics");
12
+ assert.equal(org.staffCountRange, "SIZE_51_TO_200");
13
+ assert.equal(org.locations[0].address.postalCode, "94607");
14
+ assert.equal(org.foundedOn.year, 2016);
15
+ assert.match(org.description.localized.en_US, /data platforms/);
16
+ const denied = (await get(`/rest/organizations/${ORGS.lakeview}`, [403, "ACCESS_DENIED"])).body;
17
+ assert.equal(denied.message, `Viewer don't have permission to the ADMIN_ONLY VisibilityReduction for urn:li:organization:${ORGS.lakeview}`);
18
+ await get(`/rest/organizations/${ORGS.brightline}`, [403, "ACCESS_DENIED"]);
19
+ assert.equal((await get("/rest/organizations/99999999", [404, "NOT_FOUND"])).body.message, "Organization 99999999 is inactive");
20
+ await get("/rest/organizations/abc", [400, "BAD_REQUEST"]);
21
+
22
+ const byVanity = (await get("/rest/organizations?q=vanityName&vanityName=Northwind-Analytics")).body;
23
+ assert.equal(byVanity.paging.total, 1);
24
+ assert.equal(byVanity.elements[0].id, ORGS.northwind);
25
+ assert.equal(byVanity.elements[0].description, undefined);
26
+ assert.deepEqual((await get("/rest/organizations?q=vanityName&vanityName=nobody-here")).body, {
27
+ paging: { start: 0, count: 10, links: [], total: 0 }, elements: [] });
28
+ await get("/rest/organizations?q=vanityName&vanityName=north%20wind", [400, "INVALID_VALUE_FOR_FIELD"]);
29
+ await get("/rest/organizations?q=vanityName&vanityName=%ZZ", [400]);
30
+ await get("/rest/organizations?q=parentOrganization&vanityName=x", [400, "BAD_REQUEST"]);
31
+ }
32
+
33
+ async function acls() {
34
+ const mine = (await get("/rest/organizationAcls?q=roleAssignee")).body;
35
+ assert.equal(mine.paging.total, 3);
36
+ assert.ok(mine.elements.every((acl) => acl.roleAssignee === maya && acl.organization !== undefined));
37
+ assert.equal((await get("/rest/organizationAcls?q=roleAssignee&role=ADMINISTRATOR&state=APPROVED")).body.paging.total, 1);
38
+ const paged = (await get("/rest/organizationAcls?q=roleAssignee&count=1")).body;
39
+ assert.equal(paged.elements.length, 1);
40
+ assert.equal(paged.paging.links[0].rel, "next");
41
+ const target = (await get(`/rest/organizationAcls?q=organization&organization=${enc(orgUrn(ORGS.northwind))}`)).body;
42
+ assert.equal(target.paging.total, 3);
43
+ assert.ok(target.elements.every((acl) => acl.organizationTarget === orgUrn(ORGS.northwind) && acl.organization === undefined));
44
+ assert.ok(target.elements.some((acl) => acl.state === "REVOKED"));
45
+ await get(`/rest/organizationAcls?q=organization&organization=${enc(orgUrn(ORGS.brightline))}`, [403, "ACCESS_DENIED"]);
46
+ await get(`/rest/organizationAcls?q=organization&organization=${enc(orgUrn(99999999))}`, [404, "NOT_FOUND"]);
47
+ await get("/rest/organizationAcls?q=organization", [400, "MISSING_FIELD"]);
48
+ await get("/rest/organizationAcls?q=roleAssignee&role=OWNER", [400, "INVALID_VALUE_FOR_FIELD"]);
49
+ await get(`/rest/organizationAcls?q=organization&organization=${enc(maya)}`, [400, "INVALID_URN_TYPE"]);
50
+ await get("/rest/organizationAcls?q=organization&organization=urn%3Ali%3Aorganization%3A0", [400, "INVALID_URN_ID"]);
51
+ }
52
+
53
+ async function network() {
54
+ const followers = `/rest/networkSizes/${enc(orgUrn(ORGS.northwind))}`;
55
+ assert.deepEqual((await get(`${followers}?edgeType=COMPANY_FOLLOWED_BY_MEMBER`)).body, { firstDegreeSize: 4821 });
56
+ await get(`${followers}?edgeType=CompanyFollowedByMember`, [400, "INVALID_VALUE_FOR_FIELD"]);
57
+ await get(followers, [400, "MISSING_FIELD"]);
58
+ await get(`/rest/networkSizes/${enc(orgUrn(99999999))}?edgeType=COMPANY_FOLLOWED_BY_MEMBER`, [404, "NOT_FOUND"]);
59
+ await get(`/rest/networkSizes/${enc("urn:li:share:1")}`, [400, "BAD_REQUEST"]);
60
+ assert.deepEqual((await get(`/v2/connections/${enc(maya)}`)).body, { firstDegreeSize: 7 });
61
+ await get(`/v2/connections/${enc(personUrn(PEOPLE.sam))}`, 403);
62
+ await op("network_sizes.get", { entity: "urn:li:share:1" }, "INVALID_URN_TYPE");
63
+ await op("network_sizes.get", { entity: "urn:li:person:x" }, "INVALID_URN_ID");
64
+
65
+ const page = async (start) => (await op("connections.list", { start, count: 3 })).value;
66
+ const pages = [await page(0), await page(3), await page(6)];
67
+ assert.equal(pages[0].paging.total, 7);
68
+ const names = pages.flatMap((p) => p.elements.map((e) => e.person.localizedFirstName));
69
+ assert.deepEqual(names, ["Grace", "Aiko", "Daniel", "Zoë", "Luis", "Priya", "Sam"]);
70
+ assert.deepEqual((await op("connections.list", { query: "ZOË" })).value.elements.map((e) => e.person.id), [PEOPLE.zoe]);
71
+ assert.deepEqual((await op("connections.list", { query: "data engineer" })).value.elements.map((e) => e.person.id), [PEOPLE.daniel]);
72
+ await op("connections.list", { query: "�" }, "INVALID_VALUE_FOR_FIELD");
73
+ await op("connections.list", { count: 101 }, "INVALID_VALUE_FOR_FIELD");
74
+ }
75
+
76
+ async function feed() {
77
+ const all = (await op("feed.list", { count: 50 })).value;
78
+ assert.equal(all.paging.total, 16);
79
+ assert.equal(all.viewer.connectionCount, 7);
80
+ assert.deepEqual(all.viewer.pages.map((p) => [p.name, p.roles]), [["Northwind Analytics", ["ADMINISTRATOR"]],
81
+ ["Lakeview Institute of Technology", ["ANALYST"]]]);
82
+ const ids = all.elements.map((item) => item.post.id);
83
+ for (const key of ["zoe", "sam", "brightline", "northwindArticle", "northwindPriya", "mayaConnections"]) assert.ok(ids.includes(POSTS[key].urn), key);
84
+ for (const key of ["jordan", "mayaDraft", "mayaDeleted"]) assert.ok(!ids.includes(POSTS[key].urn), key);
85
+ for (let i = 1; i < all.elements.length; i += 1) assert.ok(all.elements[i - 1].post.publishedAt >= all.elements[i].post.publishedAt);
86
+ const byId = new Map(all.elements.map((item) => [item.post.id, item]));
87
+ assert.equal(byId.get(POSTS.mayaReshare.urn).reshareOf.post.id, POSTS.sam.urn);
88
+ assert.equal(byId.get(POSTS.sam.urn).viewerReaction, "LIKE");
89
+ assert.equal(byId.get(POSTS.sam.urn).social.repostCount, 1);
90
+ assert.deepEqual(byId.get(POSTS.mayaArticle.urn).social.commentSummary, { count: 5, topLevelCount: 3 });
91
+ assert.deepEqual(byId.get(POSTS.northwindArticle.urn).actor, { urn: orgUrn(ORGS.northwind), kind: "organization", name: "Northwind Analytics",
92
+ headline: null, vanityName: "northwind-analytics" });
93
+ const first = (await op("feed.list", { count: 5 })).value;
94
+ assert.equal(first.elements.length, 5);
95
+ assert.equal(first.paging.links[0].rel, "next");
96
+ await op("feed.list", { count: 51 }, "INVALID_VALUE_FOR_FIELD");
97
+ }
98
+
99
+ async function orgsConnection() {
100
+ const target = (await get(`/rest/organizationAcls?q=organization&organization=${enc(orgUrn(ORGS.brightline))}`)).body;
101
+ assert.equal(target.paging.total, 2);
102
+ }
103
+
104
+ export const FLOWS = {
105
+ "orgs-network": async () => { await organizations(); await acls(); await network(); await feed(); },
106
+ "orgs-connection": orgsConnection,
107
+ };
@@ -0,0 +1,196 @@
1
+ // Post flows: lifecycle, validation, paging, legacy ugcPosts and visibility.
2
+ import assert from "node:assert/strict";
3
+ import { call, del, enc, get, op, orgUrn, partial, personUrn, post, textPost } from "../client.mjs";
4
+ import { ORGS, PEOPLE, POSTS } from "../fixtures.mjs";
5
+
6
+ const maya = personUrn(PEOPLE.maya);
7
+ const posts = (query) => `/rest/posts?q=author&${query}`;
8
+
9
+ async function createChecks() {
10
+ const created = await post("/rest/posts", textPost(maya, "Launching #DataOps week with the team"));
11
+ const urn = created.headers.get("x-restli-id");
12
+ assert.match(urn, /^urn:li:share:[0-9]{19}$/);
13
+ assert.equal(created.headers.get("x-linkedin-id"), urn);
14
+ const read = (await get(`/rest/posts/${enc(urn)}`)).body;
15
+ assert.equal(read.commentary, "Launching {hashtag|\\#|DataOps} week with the team");
16
+ assert.equal(read.author, maya);
17
+ assert.equal(read.lifecycleState, "PUBLISHED");
18
+ assert.equal(read.publishedAt, read.createdAt);
19
+ assert.equal(BigInt(urn.split(":").pop()) >> 22n, BigInt(read.createdAt), "id carries the creation time");
20
+ const article = await post("/rest/posts", textPost(maya, "Worth a read", {
21
+ content: { article: { source: "https://example.com/articles/lineage", title: "Lineage at scale", description: "Notes" } } }));
22
+ const reshare = await post("/rest/posts", textPost(maya, "", { reshareContext: { parent: POSTS.sam.urn } }));
23
+ const reshared = (await get(`/rest/posts/${enc(reshare.headers.get("x-restli-id"))}`)).body;
24
+ assert.deepEqual(reshared.reshareContext, { parent: POSTS.sam.urn, root: POSTS.sam.urn });
25
+ await post("/rest/posts", textPost(orgUrn(ORGS.northwind), "From the Northwind page"));
26
+ const bad = (body, expect) => post("/rest/posts", body, expect);
27
+ await bad(textPost(maya, "x", { reshareContext: { parent: POSTS.brightline.urn } }), [422, "UNPROCESSABLE_ENTITY"]);
28
+ await bad(textPost(maya, "x", { reshareContext: { parent: POSTS.mayaDraft.urn } }), [422, "UNPROCESSABLE_ENTITY"]);
29
+ await bad(textPost(maya, "x", { reshareContext: { parent: "urn:li:share:12345" } }), [404, "NOT_FOUND"]);
30
+ await bad(textPost(maya, "x", { reshareContext: {} }), [400, "MISSING_FIELD"]);
31
+ await bad(textPost(orgUrn(ORGS.brightline), "requested role only"), [403, "ACCESS_DENIED"]);
32
+ await bad(textPost(orgUrn(ORGS.lakeview), "analyst role only"), [403, "ACCESS_DENIED"]);
33
+ await bad(textPost(personUrn(PEOPLE.sam), "not me"), [403, "ACCESS_DENIED"]);
34
+ await bad(textPost(orgUrn(ORGS.northwind), "x", { visibility: "CONNECTIONS" }), [400, "INVALID_VALUE_FOR_FIELD"]);
35
+ await bad(textPost(maya, "x", { visibility: "FRIENDS" }), [400, "INVALID_VALUE_FOR_FIELD"]);
36
+ await bad(textPost(maya, "x", { lifecycleState: "DRAFT" }), [400, "INVALID_VALUE_FOR_FIELD"]);
37
+ await bad(textPost(maya, "x", { distribution: { feedDistribution: "MAIN_FEED", targetEntities: [{ geoLocations: [] }] } }), 400);
38
+ await bad(textPost(maya, "x", { distribution: { feedDistribution: "MAIN_FEED", thirdPartyDistributionChannels: ["X"] } }),
39
+ [400, "INVALID_VALUE_FOR_FIELD"]);
40
+ await bad(textPost(maya, "x", { content: { media: { category: "IMAGE" } } }), [400, "INVALID_VALUE_FOR_FIELD"]);
41
+ await bad(textPost(maya, "x", { content: { article: { source: "http://example.com", title: "t" } } }), [400, "INVALID_VALUE_FOR_FIELD"]);
42
+ await bad(textPost(maya, "x", { content: { article: { source: "https://example.com", title: " " } } }), [400, "INVALID_VALUE_BLANK_FIELD"]);
43
+ await bad(textPost(maya, "x", { content: { article: { source: "https://example.com", title: "t".repeat(401) } } }), [400, "FIELD_LENGTH_TOO_LONG"]);
44
+ await bad(textPost(maya, "x".repeat(3001)), [400, "FIELD_LENGTH_TOO_LONG"]);
45
+ await bad(textPost(maya, `${"a".repeat(2990)} #tag`), [400, "FIELD_LENGTH_TOO_LONG"]);
46
+ await bad(textPost(maya, " "), [400, "INVALID_VALUE_BLANK_FIELD"]);
47
+ for (const field of ["author", "visibility", "distribution", "lifecycleState"]) {
48
+ const body = textPost(maya, "x");
49
+ delete body[field];
50
+ await bad(body, [400, "MISSING_FIELD"]);
51
+ }
52
+ await bad(textPost(maya, "x", { distribution: {} }), [400, "MISSING_FIELD"]);
53
+ await bad(textPost("urn:li:share:1", "x"), [400, "INVALID_URN_TYPE"]);
54
+ await bad(textPost("urn:li:person:short", "x"), [400, "INVALID_URN_ID"]);
55
+ await bad({ ...textPost(maya, "x"), adContext: {} }, [400, "BAD_REQUEST"]);
56
+ return { urn, article: article.headers.get("x-restli-id") };
57
+ }
58
+
59
+ async function findChecks() {
60
+ const all = [];
61
+ for (const sort of ["LAST_MODIFIED", "CREATED"]) {
62
+ const first = (await get(posts(`author=${enc(maya)}&sortBy=${sort}&count=10`))).body;
63
+ assert.equal(first.paging.total, 14);
64
+ assert.equal(first.elements.length, 10);
65
+ assert.deepEqual(first.paging.links.map((l) => l.rel), ["next"]);
66
+ const second = (await get(posts(`author=${enc(maya)}&sortBy=${sort}&count=10&start=10`))).body;
67
+ assert.equal(second.elements.length, 4);
68
+ assert.deepEqual(second.paging.links.map((l) => l.rel), ["prev"]);
69
+ const list = [...first.elements, ...second.elements];
70
+ assert.equal(new Set(list.map((p) => p.id)).size, 14);
71
+ const key = sort === "CREATED" ? "createdAt" : "lastModifiedAt";
72
+ for (let i = 1; i < list.length; i += 1) assert.ok(list[i - 1][key] >= list[i][key], `${sort} order`);
73
+ assert.ok(!list.some((p) => p.id === POSTS.mayaDraft.urn || p.id === POSTS.mayaDeleted.urn));
74
+ all.push(list.findIndex((p) => p.id === POSTS.mayaEdited.urn));
75
+ }
76
+ assert.notEqual(all[0], all[1], "LAST_MODIFIED and CREATED orders differ");
77
+ const author = (await get(posts(`author=${enc(maya)}&viewContext=AUTHOR&count=100`))).body;
78
+ assert.equal(author.paging.total, 15);
79
+ assert.ok(author.elements.some((p) => p.id === POSTS.mayaDraft.urn && p.publishedAt === undefined));
80
+ assert.equal((await get(posts(`author=${enc(orgUrn(ORGS.northwind))}`))).body.paging.total, 3);
81
+ assert.equal((await get(posts(`author=${enc(orgUrn(ORGS.lakeview))}`))).body.paging.total, 0);
82
+ await get(posts(`author=${enc(orgUrn(ORGS.brightline))}`), [403, "ACCESS_DENIED"]);
83
+ await get(posts(`author=${enc(orgUrn(99999999))}`), [404, "NOT_FOUND"]);
84
+ await get(posts(`author=${enc(personUrn(PEOPLE.sam))}`), [403, "ACCESS_DENIED"]);
85
+ await get("/rest/posts?q=author", [400, "MISSING_FIELD"]);
86
+ await get(posts(`author=${enc(POSTS.sam.urn)}`), [400, "INVALID_URN_TYPE"]);
87
+ await get(posts("author=urn%3Ali%3Aperson%3Ax"), [400, "INVALID_URN_ID"]);
88
+ await get(posts(`author=${enc(maya)}&count=0`), [400, "INVALID_VALUE_FOR_FIELD"]);
89
+ await get(posts(`author=${enc(maya)}&sortBy=RELEVANCE`), [400, "INVALID_VALUE_FOR_FIELD"]);
90
+ await get(posts(`author=${enc(maya)}&count=abc`), [400, "BAD_REQUEST"]);
91
+ await get(`/rest/posts?q=dscAdAccount&author=${enc(maya)}`, [400, "BAD_REQUEST"]);
92
+ }
93
+
94
+ const path = (urn) => `/rest/posts/${enc(urn)}`;
95
+
96
+ async function readUpdateDelete(created) {
97
+ await get(path(POSTS.mayaDraft.urn), [404, "NOT_FOUND"]);
98
+ assert.equal((await get(`${path(POSTS.mayaDraft.urn)}?viewContext=AUTHOR`)).body.lifecycleState, "DRAFT");
99
+ await get(`${path(POSTS.mayaDraft.urn)}?viewContext=OWNER`, [400, "INVALID_VALUE_FOR_FIELD"]);
100
+ await get(path(POSTS.jordan.urn), [403, "ACCESS_DENIED"]);
101
+ await get(path(POSTS.mayaDeleted.urn), [404, "NOT_FOUND"]);
102
+ await get(path("urn:li:share:abc"), [400, "BAD_REQUEST"]);
103
+ assert.equal((await get(path(POSTS.mayaLong.urn))).body.commentary.length, 3000);
104
+ await op("posts.get", { postUrn: maya }, "INVALID_URN_TYPE");
105
+ await op("posts.get", { postUrn: "urn:li:share:" }, "INVALID_URN_ID");
106
+
107
+ await partial(path(created.urn), { patch: { $set: { commentary: "Edited #DataOps" } } });
108
+ const edited = (await get(path(created.urn))).body;
109
+ assert.equal(edited.commentary, "Edited {hashtag|\\#|DataOps}");
110
+ assert.equal(edited.lifecycleStateInfo.isEditedByAuthor, true);
111
+ await partial(path(POSTS.mayaDraft.urn), { patch: { $set: { lifecycleState: "PUBLISHED" } } });
112
+ const published = (await get(path(POSTS.mayaDraft.urn))).body;
113
+ assert.equal(published.lifecycleState, "PUBLISHED");
114
+ assert.equal(typeof published.publishedAt, "number");
115
+ assert.equal(published.lifecycleStateInfo.isEditedByAuthor, false);
116
+ await partial(path(created.urn), { patch: { $set: { lifecycleState: "DRAFT" } } }, [400, "INVALID_VALUE_FOR_FIELD"]);
117
+ await partial(path(created.urn), { patch: { $set: { foo: 1 } } }, [400, "INVALID_VALUE_FOR_FIELD"]);
118
+ await partial(path(created.urn), { patch: { $delete: ["commentary"] } }, [400, "INVALID_VALUE_FOR_FIELD"]);
119
+ await partial(path(created.urn), { patch: { $set: { contentCallToActionLabel: "LEARN_MORE" } } }, [400, "INVALID_VALUE_FOR_FIELD"]);
120
+ await partial(path(created.urn), { patch: { $set: {} } }, [400, "MISSING_FIELD"]);
121
+ await partial(path(created.urn), { patch: { $set: { commentary: "y".repeat(3001) } } }, [400, "FIELD_LENGTH_TOO_LONG"]);
122
+ await partial(path(created.urn), { patch: { $set: { commentary: " " } } }, [400, "INVALID_VALUE_BLANK_FIELD"]);
123
+ await call("POST", path(created.urn), [400, "BAD_REQUEST"], { json: { patch: { $set: { commentary: "no method header" } } } });
124
+ await partial(path(POSTS.sam.urn), { patch: { $set: { commentary: "not mine" } } }, [403, "ACCESS_DENIED"]);
125
+ await partial(path(POSTS.mayaDeleted.urn), { patch: { $set: { commentary: "gone" } } }, [404, "NOT_FOUND"]);
126
+ await op("posts.update", { postUrn: "urn:li:comment:1", commentary: "x" }, "INVALID_URN_TYPE");
127
+ await op("posts.update", { postUrn: "urn:li:ugcPost:x", commentary: "x" }, "INVALID_URN_ID");
128
+
129
+ await del(path(created.article));
130
+ await del(path(POSTS.mayaArticle.urn));
131
+ await del(path(POSTS.mayaArticle.urn));
132
+ await get(path(POSTS.mayaArticle.urn), [404, "NOT_FOUND"]);
133
+ await op("comments.list", { target: POSTS.mayaArticle.urn }, "NOT_FOUND");
134
+ await del(path("urn:li:share:12345"), [404, "NOT_FOUND"]);
135
+ await del(path(POSTS.sam.urn), [403, "ACCESS_DENIED"]);
136
+ await op("posts.delete", { postUrn: maya }, "INVALID_URN_TYPE");
137
+ await op("posts.delete", { postUrn: "urn:li:share:1x" }, "INVALID_URN_ID");
138
+ }
139
+
140
+ async function lifecycle() {
141
+ const created = await createChecks();
142
+ await findChecks();
143
+ await readUpdateDelete(created);
144
+ }
145
+
146
+ const ugcBody = (extra = {}) => ({
147
+ author: maya, lifecycleState: "PUBLISHED", visibility: { "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC" },
148
+ specificContent: { "com.linkedin.ugc.ShareContent": { shareCommentary: { text: "Hello from the legacy API" }, shareMediaCategory: "NONE", ...extra } },
149
+ });
150
+
151
+ async function ugcLegacy() {
152
+ const text = await post("/v2/ugcPosts", ugcBody());
153
+ assert.match(text.body.id, /^urn:li:ugcPost:[0-9]{19}$/);
154
+ assert.equal(text.headers.get("x-restli-id"), text.body.id);
155
+ const read = (await get(path(text.body.id))).body;
156
+ assert.equal(read.commentary, "Hello from the legacy API");
157
+ assert.equal(read.visibility, "PUBLIC");
158
+ const article = await post("/v2/ugcPosts", ugcBody({ shareMediaCategory: "ARTICLE",
159
+ media: [{ status: "READY", originalUrl: "https://example.com/legacy", title: { text: "Legacy article" }, description: { text: "Shared" } }] }));
160
+ assert.deepEqual((await get(path(article.body.id))).body.content,
161
+ { article: { source: "https://example.com/legacy", title: "Legacy article", description: "Shared" } });
162
+ await post("/v2/ugcPosts", ugcBody({ shareMediaCategory: "IMAGE" }), 400);
163
+ const { author, ...noAuthor } = ugcBody();
164
+ void author;
165
+ await post("/v2/ugcPosts", noAuthor, 400);
166
+ await del(`/v2/ugcPosts/${enc(text.body.id)}`);
167
+ await del(`/v2/ugcPosts/${enc(text.body.id)}`);
168
+ await get(path(text.body.id), [404, "NOT_FOUND"]);
169
+ await del(`/v2/ugcPosts/${enc("urn:li:ugcPost:12345")}`, 404);
170
+ }
171
+
172
+ async function visibilityStranger() {
173
+ await get(path(POSTS.mayaConnections.urn), [403, "ACCESS_DENIED"]);
174
+ await get(`/rest/socialActions/${enc(POSTS.mayaConnections.urn)}/comments`, [403, "ACCESS_DENIED"]);
175
+ await get(`/rest/reactions/${enc(`(entity:${POSTS.mayaConnections.activity})`)}?q=entity`, [403, "ACCESS_DENIED"]);
176
+ await get(`/rest/socialMetadata/${enc(POSTS.mayaConnections.urn)}`, [403, "ACCESS_DENIED"]);
177
+ assert.equal((await get(path(POSTS.mayaPlain.urn))).body.visibility, "PUBLIC");
178
+ assert.equal((await get(path(POSTS.mayaLoggedIn.urn))).body.visibility, "LOGGED_IN");
179
+ const info = (await get("/v2/userinfo")).body;
180
+ assert.equal(info.email, undefined);
181
+ assert.equal(info.picture, undefined);
182
+ assert.equal(info.locale, "es-ES");
183
+ assert.equal((await get(`/v2/connections/${enc(personUrn(PEOPLE.tomas))}`)).body.firstDegreeSize, 0);
184
+ }
185
+
186
+ async function visibilityConnection() {
187
+ assert.equal((await get(path(POSTS.mayaConnections.urn))).body.visibility, "CONNECTIONS");
188
+ await partial(path(POSTS.mayaConnections.urn), { patch: { $set: { commentary: "hijack" } } }, [403, "ACCESS_DENIED"]);
189
+ await del(path(POSTS.mayaConnections.urn), [403, "ACCESS_DENIED"]);
190
+ await get(posts(`author=${enc(maya)}`), [403, "ACCESS_DENIED"]);
191
+ await get(path(POSTS.mayaDraft.urn), [404, "NOT_FOUND"]);
192
+ }
193
+
194
+ export const FLOWS = {
195
+ "posts-lifecycle": lifecycle, "ugc-legacy": ugcLegacy, "visibility-stranger": visibilityStranger, "visibility-connection": visibilityConnection,
196
+ };
@@ -0,0 +1,101 @@
1
+ // Reaction and social metadata flows.
2
+ import assert from "node:assert/strict";
3
+ import { del, enc, get, op, orgUrn, personUrn, post } from "../client.mjs";
4
+ import { COMMENTS, ORGS, PEOPLE, POSTS } from "../fixtures.mjs";
5
+
6
+ const maya = personUrn(PEOPLE.maya);
7
+ const plain = POSTS.mayaPlain;
8
+ const art = POSTS.mayaArticle;
9
+ const react = (actor, body, expect = 201) => post(`/rest/reactions?actor=${enc(actor)}`, body, expect);
10
+ const key = (actor, entity) => `/rest/reactions/${enc(`(actor:${actor},entity:${entity})`)}`;
11
+ const finder = (entity, query = "") => `/rest/reactions/${enc(`(entity:${entity})`)}?q=entity${query}`;
12
+ const meta = (entity) => `/rest/socialMetadata/${enc(entity)}`;
13
+
14
+ async function reactions() {
15
+ const first = (await react(maya, { root: plain.urn, reactionType: "LIKE" })).body;
16
+ assert.equal(first.id, `urn:li:reaction:(${maya},${plain.activity})`);
17
+ assert.equal(first.root, plain.activity);
18
+ for (const type of ["PRAISE", "APPRECIATION", "EMPATHY", "INTEREST", "ENTERTAINMENT"]) {
19
+ assert.equal((await react(maya, { root: plain.urn, reactionType: type })).body.reactionType, type);
20
+ }
21
+ const replay = (await react(maya, { root: plain.urn, reactionType: "ENTERTAINMENT" })).body;
22
+ assert.equal(replay.created.time, first.created.time);
23
+ await react(maya, { root: plain.urn, reactionType: "MAYBE" }, [400, "INVALID_VALUE_FOR_FIELD"]);
24
+ await react(maya, { root: plain.urn, reactionType: "LOVE" }, [400, "INVALID_VALUE_FOR_FIELD"]);
25
+ await post("/rest/reactions", { root: plain.urn, reactionType: "LIKE" }, [400, "MISSING_FIELD"]);
26
+ await react(maya, { reactionType: "LIKE" }, [400, "MISSING_FIELD"]);
27
+ await react(maya, { root: plain.urn }, [400, "MISSING_FIELD"]);
28
+ assert.equal((await react(maya, { root: COMMENTS.priyaOnArticle.urn, reactionType: "APPRECIATION" })).body.root, COMMENTS.priyaOnArticle.urn);
29
+ const asPage = (await react(orgUrn(ORGS.northwind), { root: POSTS.northwindArticle.urn, reactionType: "LIKE" })).body;
30
+ assert.equal(asPage.created.impersonator, maya);
31
+ await react(orgUrn(ORGS.brightline), { root: plain.urn, reactionType: "LIKE" }, [403, "ACCESS_DENIED"]);
32
+ await react(personUrn(PEOPLE.sam), { root: plain.urn, reactionType: "LIKE" }, [403, "ACCESS_DENIED"]);
33
+ await react(maya, { root: "urn:li:share:12345", reactionType: "LIKE" }, [404, "NOT_FOUND"]);
34
+ await react(maya, { root: POSTS.mayaDraft.urn, reactionType: "LIKE" }, [404, "NOT_FOUND"]);
35
+ await react(maya, { root: POSTS.jordan.urn, reactionType: "LIKE" }, [403, "ACCESS_DENIED"]);
36
+ await op("reactions.create", { actor: "urn:li:share:1", root: plain.urn, reactionType: "LIKE" }, "INVALID_URN_TYPE");
37
+ await op("reactions.create", { actor: maya, root: "urn:li:share:x", reactionType: "LIKE" }, "INVALID_URN_ID");
38
+
39
+ const single = (await get(key(maya, plain.activity))).body;
40
+ assert.equal(single.reactionType, "ENTERTAINMENT");
41
+ assert.equal(single.paging, undefined);
42
+ await get(key(personUrn(PEOPLE.sam), plain.activity), [404, "NOT_FOUND"]);
43
+ await get(`/rest/reactions/${enc(`(entity:${art.activity})`)}`, [400, "BAD_REQUEST"]);
44
+ const newest = (await get(finder(art.activity, "&count=2"))).body;
45
+ assert.equal(newest.paging.total, 6);
46
+ assert.deepEqual(newest.elements.map((r) => r.reactionType), ["ENTERTAINMENT", "INTEREST"]);
47
+ assert.equal(newest.paging.links[0].rel, "next");
48
+ const oldest = (await get(finder(art.activity, `&sort=${enc("(value:CHRONOLOGICAL)")}&count=2&start=4`))).body;
49
+ assert.deepEqual(oldest.elements.map((r) => r.reactionType), ["INTEREST", "ENTERTAINMENT"]);
50
+ assert.equal((await get(finder(art.activity, `&sort=${enc("(value:RELEVANCE)")}`))).body.elements.length, 6);
51
+ await get(finder(art.activity, `&sort=${enc("(value:FOO)")}`), [400, "INVALID_VALUE_FOR_FIELD"]);
52
+ await get(finder(art.activity, "&count=0"), [400, "INVALID_VALUE_FOR_FIELD"]);
53
+ await get(finder("urn:li:activity:12345"), [404, "NOT_FOUND"]);
54
+ await get(finder(POSTS.jordan.activity), [403, "ACCESS_DENIED"]);
55
+ await op("reactions.list", { entity: art.activity, single: true }, "MISSING_FIELD");
56
+ await op("reactions.list", { entity: maya }, "INVALID_URN_TYPE");
57
+ await op("reactions.list", { entity: "urn:li:activity:" }, "INVALID_URN_ID");
58
+
59
+ await del(key(maya, plain.activity));
60
+ await del(key(maya, plain.activity), [404, "NOT_FOUND"]);
61
+ await del(key(personUrn(PEOPLE.sam), art.activity), [403, "ACCESS_DENIED"]);
62
+ await del(`/rest/reactions/${enc("(actor:x)")}`, [400, "BAD_REQUEST"]);
63
+ await op("reactions.delete", { actor: "urn:li:share:1", entity: plain.activity }, "INVALID_URN_TYPE");
64
+ await op("reactions.delete", { actor: maya, entity: "urn:li:activity:" }, "INVALID_URN_ID");
65
+ }
66
+
67
+ async function metadata() {
68
+ const post1 = (await get(meta(art.urn))).body;
69
+ assert.deepEqual(post1.commentSummary, { count: 5, topLevelCount: 3 });
70
+ assert.equal(post1.commentsState, "OPEN");
71
+ assert.deepEqual(Object.keys(post1.reactionSummaries), ["LIKE", "PRAISE", "APPRECIATION", "EMPATHY", "INTEREST", "ENTERTAINMENT"]);
72
+ const onComment = (await get(meta(COMMENTS.samOnArticle.urn))).body;
73
+ assert.deepEqual(onComment.commentSummary, { count: 1, topLevelCount: 1 });
74
+ assert.deepEqual(onComment.reactionSummaries, { LIKE: { reactionType: "LIKE", count: 1 } });
75
+ await get(meta("urn:li:share:12345"), [404, "NOT_FOUND"]);
76
+ await get(meta(POSTS.jordan.urn), [403, "ACCESS_DENIED"]);
77
+ await get(meta("urn:li:share:x"), [400, "BAD_REQUEST"]);
78
+ await op("social_metadata.get", { entity: maya }, "INVALID_URN_TYPE");
79
+ await op("social_metadata.get", { entity: "urn:li:share:" }, "INVALID_URN_ID");
80
+
81
+ const set = (entity, actor, state, expect = 202) => post(`${meta(entity)}?actor=${enc(actor)}`, { patch: { $set: { commentsState: state } } }, expect);
82
+ await set(art.urn, maya, "CLOSED");
83
+ const closed = (await get(meta(art.urn))).body;
84
+ assert.equal(closed.commentsState, "CLOSED");
85
+ assert.deepEqual(closed.commentSummary, { count: 0, topLevelCount: 0 });
86
+ assert.equal(Object.keys(closed.reactionSummaries).length, 6);
87
+ const closedAgain = await op("social_metadata.set_comments_state", { entity: art.urn, actor: maya, commentsState: "CLOSED" });
88
+ assert.equal(closedAgain.value.deletedComments, 0);
89
+ await set(art.urn, maya, "OPEN");
90
+ assert.equal((await get(meta(art.urn))).body.commentsState, "OPEN");
91
+ await set(COMMENTS.zoeOnArticle.urn, maya, "CLOSED", [400, "BAD_REQUEST"]);
92
+ await op("social_metadata.set_comments_state", { entity: COMMENTS.zoeOnArticle.urn, actor: maya, commentsState: "CLOSED" }, "INVALID_URN_TYPE");
93
+ await op("social_metadata.set_comments_state", { entity: "urn:li:activity:", actor: maya, commentsState: "CLOSED" }, "INVALID_URN_ID");
94
+ await set(art.urn, maya, "PROCESSING", [400, "INVALID_VALUE_FOR_FIELD"]);
95
+ await post(meta(art.urn), { patch: { $set: { foo: "bar" } } }, [400, "INVALID_VALUE_FOR_FIELD"]);
96
+ await set(art.urn, orgUrn(ORGS.northwind), "CLOSED", [403, "ACCESS_DENIED"]);
97
+ await post(meta(art.urn), { patch: { $set: { commentsState: "CLOSED" } } }, [400, "MISSING_FIELD"]);
98
+ await set("urn:li:share:12345", maya, "CLOSED", [404, "NOT_FOUND"]);
99
+ }
100
+
101
+ export const FLOWS = { "reactions-metadata": async () => { await reactions(); await metadata(); } };
@@ -0,0 +1,62 @@
1
+ // Wire-level errors: LinkedIn-Version, X-RestLi-Method, batch get, malformed bodies and query strings, on every /rest route.
2
+ import assert from "node:assert/strict";
3
+ import { call, enc, get, op, orgUrn, personUrn, post, textPost } from "../client.mjs";
4
+ import { COMMENTS, ORGS, PEOPLE, POSTS } from "../fixtures.mjs";
5
+
6
+ const maya = personUrn(PEOPLE.maya);
7
+ const social = (target) => `/rest/socialActions/${enc(target)}/comments`;
8
+
9
+ // [label, method, path, body, Rest.li method]
10
+ const ROUTES = [
11
+ ["create-post", "POST", "/rest/posts", textPost(maya, "never stored"), "CREATE"],
12
+ ["get-post", "GET", `/rest/posts/${enc(POSTS.mayaPlain.urn)}`, undefined, "GET"],
13
+ ["find-posts", "GET", `/rest/posts?q=author&author=${enc(maya)}`, undefined, "FINDER"],
14
+ ["update-post", "POST", `/rest/posts/${enc(POSTS.mayaPlain.urn)}`, { patch: { $set: { commentary: "never" } } }, "PARTIAL_UPDATE"],
15
+ ["delete-post", "DELETE", `/rest/posts/${enc(POSTS.mayaPlain.urn)}`, undefined, "DELETE"],
16
+ ["create-comment", "POST", social(POSTS.mayaPlain.urn), { actor: maya, message: { text: "never" } }, "CREATE"],
17
+ ["list-comments", "GET", social(POSTS.mayaArticle.urn), undefined, "GET_ALL"],
18
+ ["get-comment", "GET", `${social(POSTS.mayaArticle.urn)}/${COMMENTS.samOnArticle.id}`, undefined, "GET"],
19
+ ["update-comment", "POST", `${social(POSTS.zoe.urn)}/${COMMENTS.mayaOnZoe.id}`, { patch: { message: { $set: { text: "never" } } } }, "PARTIAL_UPDATE"],
20
+ ["delete-comment", "DELETE", `${social(POSTS.zoe.urn)}/${COMMENTS.mayaOnZoe.id}`, undefined, "DELETE"],
21
+ ["create-reaction", "POST", `/rest/reactions?actor=${enc(maya)}`, { root: POSTS.mayaPlain.urn, reactionType: "LIKE" }, "CREATE"],
22
+ ["get-reactions", "GET", `/rest/reactions/${enc(`(entity:${POSTS.mayaArticle.activity})`)}?q=entity`, undefined, "FINDER"],
23
+ ["delete-reaction", "DELETE", `/rest/reactions/${enc(`(actor:${maya},entity:${POSTS.zoe.activity})`)}`, undefined, "DELETE"],
24
+ ["get-social-metadata", "GET", `/rest/socialMetadata/${enc(POSTS.mayaArticle.urn)}`, undefined, "GET"],
25
+ ["update-social-metadata", "POST", `/rest/socialMetadata/${enc(POSTS.mayaPlain.urn)}?actor=${enc(maya)}`,
26
+ { patch: { $set: { commentsState: "CLOSED" } } }, "PARTIAL_UPDATE"],
27
+ ["get-organization", "GET", `/rest/organizations/${ORGS.northwind}`, undefined, "GET"],
28
+ ["find-organizations", "GET", "/rest/organizations?q=vanityName&vanityName=northwind-analytics", undefined, "FINDER"],
29
+ ["find-organization-acls", "GET", "/rest/organizationAcls?q=roleAssignee", undefined, "FINDER"],
30
+ ["get-network-size", "GET", `/rest/networkSizes/${enc(orgUrn(ORGS.northwind))}?edgeType=COMPANY_FOLLOWED_BY_MEMBER`, undefined, "GET"],
31
+ ];
32
+
33
+ async function wireErrors() {
34
+ for (const [label, method, path, json, restli] of ROUTES) {
35
+ const options = (extra) => ({ json, restli, ...extra });
36
+ const missing = (await call(method, path, [400, "VERSION_MISSING"], options({ version: null }))).body;
37
+ assert.match(missing.message, /Linkedin-Version header/, label);
38
+ const inactive = (await call(method, path, [426, "NONEXISTENT_VERSION"], options({ version: "202508" }))).body;
39
+ assert.equal(inactive.message, "Requested version 202508 is not active", label);
40
+ await call(method, path, [426, "NONEXISTENT_VERSION"], options({ version: "abc" }));
41
+ await call(method, path, [400, "BAD_REQUEST"], options({ restli: "BATCH_GET" }));
42
+ await call(method, `${path}${path.includes("?") ? "&" : "?"}ids=List(1,2)`, [400, "BAD_REQUEST"], options({}));
43
+ }
44
+ const deep = `{"author":"${maya}","commentary":"x","visibility":"PUBLIC","distribution":{"feedDistribution":"MAIN_FEED"},"lifecycleState":"PUBLISHED",`
45
+ + `"content":${"[".repeat(600)}${"]".repeat(600)}}`;
46
+ await post("/rest/posts", deep, [400, "BAD_REQUEST"]);
47
+ // The framework drops an own `__proto__` member before the codec runs; the request still fails validation and pollutes nothing.
48
+ await post("/rest/posts", `{"__proto__":{"polluted":1},"author":"${maya}"}`, 400);
49
+ await post("/rest/posts", "[1,2,3]", [400, "BAD_REQUEST"]);
50
+ assert.equal({}.polluted, undefined);
51
+ await get(`/rest/posts?q=author&${Array.from({ length: 21 }, () => `author=${enc(maya)}`).join("&")}`, [400, "BAD_REQUEST"]);
52
+ await get("/rest/posts?q=author&author=%FF", [400, "BAD_REQUEST"]);
53
+ await get("/rest/posts?q=author&author=%ZZ", [400, "INVALID_URN_ID"]);
54
+ await post("/v2/ugcPosts", "[]", 400);
55
+ await call("DELETE", `/v2/ugcPosts/${enc("urn:li:ugcPost:abc")}`, 400);
56
+ await get(`/v2/people/${enc("(id:x")}`, 400);
57
+ await op("feed.list", { count: 0 }, "INVALID_VALUE_FOR_FIELD");
58
+ await op("connections.list", { count: 0 }, "INVALID_VALUE_FOR_FIELD");
59
+ assert.equal((await get(`/rest/posts/${enc(POSTS.mayaPlain.urn)}`)).body.commentary.startsWith("Shipping"), true);
60
+ }
61
+
62
+ export const FLOWS = { "wire-errors": wireErrors };