@sanity-labs/client-fake-for-test 0.9.1 → 0.11.0

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.
package/dist/index.d.cts CHANGED
@@ -108,7 +108,8 @@ export declare interface CreateTestClientOptions {
108
108
  /**
109
109
  * Opt-in access-control simulation. When set, the client exposes
110
110
  * `request({ url })` that serves `/users/me` (host-aware), the
111
- * configured ACL path, and `/projects/<projectId>/users/<ids>`,
111
+ * configured ACL path, `/projects/<projectId>` (members), and
112
+ * `/projects/<projectId>/users/<ids>`,
112
113
  * AND every mutation is gated through the supplied `grants`
113
114
  * — writes that the grants don't permit throw a `WriteAccessDeniedError`
114
115
  * shaped like Sanity's 403. Lets tests demonstrate the full
@@ -594,6 +595,14 @@ export declare type PatchOp =
594
595
 
595
596
  export declare type Perspective = "raw" | "published" | "drafts" | string[];
596
597
 
598
+ /** One row of the `/projects/<projectId>` members response. */
599
+ export declare interface ProjectMemberRecord {
600
+ id: string;
601
+ isRobot?: boolean;
602
+ /** Project-scoped roles in canonical order, independent of registration order. */
603
+ roles?: NonNullable<CurrentUser["roles"]>;
604
+ }
605
+
597
606
  /**
598
607
  * A row served by the project-users directory endpoint
599
608
  * (`/projects/<projectId>/users/<id>`), mirroring Sanity's project user
package/dist/index.d.ts CHANGED
@@ -108,7 +108,8 @@ export declare interface CreateTestClientOptions {
108
108
  /**
109
109
  * Opt-in access-control simulation. When set, the client exposes
110
110
  * `request({ url })` that serves `/users/me` (host-aware), the
111
- * configured ACL path, and `/projects/<projectId>/users/<ids>`,
111
+ * configured ACL path, `/projects/<projectId>` (members), and
112
+ * `/projects/<projectId>/users/<ids>`,
112
113
  * AND every mutation is gated through the supplied `grants`
113
114
  * — writes that the grants don't permit throw a `WriteAccessDeniedError`
114
115
  * shaped like Sanity's 403. Lets tests demonstrate the full
@@ -594,6 +595,14 @@ export declare type PatchOp =
594
595
 
595
596
  export declare type Perspective = "raw" | "published" | "drafts" | string[];
596
597
 
598
+ /** One row of the `/projects/<projectId>` members response. */
599
+ export declare interface ProjectMemberRecord {
600
+ id: string;
601
+ isRobot?: boolean;
602
+ /** Project-scoped roles in canonical order, independent of registration order. */
603
+ roles?: NonNullable<CurrentUser["roles"]>;
604
+ }
605
+
597
606
  /**
598
607
  * A row served by the project-users directory endpoint
599
608
  * (`/projects/<projectId>/users/<id>`), mirroring Sanity's project user
package/dist/index.js CHANGED
@@ -62,6 +62,28 @@ function defaultAclPath(projectId, dataset) {
62
62
  function projectUserOf(identity, projectId) {
63
63
  return identity.usersByProject?.[projectId] ?? identity.user;
64
64
  }
65
+ function consistentProjectRecords(args) {
66
+ const byId = /* @__PURE__ */ new Map();
67
+ for (const record of args.records) {
68
+ const existing = byId.get(record.id);
69
+ if (existing !== void 0 && JSON.stringify(existing) !== JSON.stringify(record))
70
+ throw fakeError(
71
+ `request(): two registered identities resolve to project user "${record.id}" in project "${args.projectId}" with different records. The project records cannot be served unambiguously \u2014 fix the identity registrations so duplicate ids carry identical records.`
72
+ );
73
+ byId.set(record.id, record);
74
+ }
75
+ return byId;
76
+ }
77
+ function canonicalProjectRoles(roles) {
78
+ return roles.map((role) => ({
79
+ name: role.name,
80
+ ...role.title !== void 0 ? { title: role.title } : {},
81
+ ...role.description !== void 0 ? { description: role.description } : {}
82
+ })).toSorted((left, right) => {
83
+ const leftKey = JSON.stringify(left), rightKey = JSON.stringify(right);
84
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
85
+ });
86
+ }
65
87
  function serveProjectUsers(args) {
66
88
  const { requestedIds, pathProjectId, identities } = args, toRecord = (identity) => {
67
89
  const projectUser = projectUserOf(identity, pathProjectId);
@@ -71,33 +93,38 @@ function serveProjectUsers(args) {
71
93
  displayName: projectUser.name ?? projectUser.id,
72
94
  ...projectUser.email !== void 0 ? { email: projectUser.email } : {}
73
95
  };
74
- }, byId = /* @__PURE__ */ new Map();
75
- for (const identity of identities) {
76
- const resolvedId = projectUserOf(identity, pathProjectId).id, existing = byId.get(resolvedId);
77
- if (existing !== void 0 && JSON.stringify(toRecord(existing)) !== JSON.stringify(toRecord(identity)))
78
- throw fakeError(
79
- `request(): two registered identities resolve to project user "${resolvedId}" in project "${pathProjectId}" with different records. The directory cannot answer unambiguously \u2014 fix the identity registrations so duplicate ids carry identical records.`
80
- );
81
- byId.set(resolvedId, identity);
82
- }
83
- const ids = requestedIds.split(","), [firstId] = ids;
96
+ }, byId = consistentProjectRecords({
97
+ projectId: pathProjectId,
98
+ records: identities.map(toRecord)
99
+ }), ids = requestedIds.split(","), [firstId] = ids;
84
100
  if (ids.length === 1 && firstId !== void 0) {
85
- const identity = byId.get(firstId);
86
- if (identity === void 0)
101
+ const record = byId.get(firstId);
102
+ if (record === void 0)
87
103
  throw Object.assign(
88
104
  fakeError(
89
105
  `request(): no registered identity resolves to project user "${firstId}" in project "${pathProjectId}". Register it via createTestClient({ identities }) or addIdentity().`
90
106
  ),
91
107
  { statusCode: 404 }
92
108
  );
93
- return toRecord(identity);
109
+ return record;
94
110
  }
95
111
  return ids.flatMap((id) => {
96
- const identity = byId.get(id);
97
- return identity === void 0 ? [] : [toRecord(identity)];
112
+ const record = byId.get(id);
113
+ return record === void 0 ? [] : [record];
114
+ });
115
+ }
116
+ const PROJECT_USERS_PATH = /^\/projects\/([^/]+)\/users\/([^/]+)$/, PROJECT_PATH = /^\/projects\/([^/]+)$/;
117
+ function serveProject(args) {
118
+ const records = args.identities.map((identity) => {
119
+ const projectUser = projectUserOf(identity, args.pathProjectId);
120
+ return {
121
+ id: projectUser.id,
122
+ ...projectUser.id.startsWith("p-") ? { isRobot: !0 } : {},
123
+ ...projectUser.roles !== void 0 ? { roles: canonicalProjectRoles(projectUser.roles) } : {}
124
+ };
98
125
  });
126
+ return { members: [...consistentProjectRecords({ projectId: args.pathProjectId, records }).values()] };
99
127
  }
100
- const PROJECT_USERS_PATH = /^\/projects\/([^/]+)\/users\/([^/]+)$/;
101
128
  function makeRequestImpl(ctx) {
102
129
  const { getAccess, projectId, dataset, useProjectHostname, listIdentities } = ctx;
103
130
  return async (opts) => {
@@ -113,8 +140,14 @@ function makeRequestImpl(ctx) {
113
140
  pathProjectId,
114
141
  identities: listIdentities()
115
142
  });
143
+ const project = path?.match(PROJECT_PATH), [, membersProjectId] = project ?? [];
144
+ if (membersProjectId !== void 0)
145
+ return serveProject({
146
+ pathProjectId: membersProjectId,
147
+ identities: listIdentities()
148
+ });
116
149
  throw fakeError(
117
- `request(): no handler for path "${path}". With access control configured, the fake serves "/users/me", "${aclPath}", and "/projects/<projectId>/users/<ids>". For any other endpoint, register a stub (client.stub("request", fn)) or intercept the HTTP call with a tool like MSW.`
150
+ `request(): no handler for path "${path}". With access control configured, the fake serves "/users/me", "${aclPath}", "/projects/<projectId>" (members), and "/projects/<projectId>/users/<ids>". For any other endpoint, register a stub (client.stub("request", fn)) or intercept the HTTP call with a tool like MSW.`
118
151
  );
119
152
  };
120
153
  }
@@ -825,7 +858,7 @@ const ISSUES_URL = "https://github.com/sanity-labs/client-fake-for-test/issues",
825
858
  },
826
859
  request: {
827
860
  what: "generic HTTP request hook \u2014 only wired up when the handle has an acting identity",
828
- workaround: "Create the client with `accessControl`, or bind a registered token (`identities` + `token` / `withConfig({ token })`), to enable request() (it serves /users/me, the ACL path, and /projects/<projectId>/users/<ids>). Or register a stub."
861
+ workaround: "Create the client with `accessControl`, or bind a registered token (`identities` + `token` / `withConfig({ token })`), to enable request() (it serves /users/me, the ACL path, /projects/<projectId>, and /projects/<projectId>/users/<ids>). Or register a stub."
829
862
  },
830
863
  dryRun: {
831
864
  what: "not a method \u2014 did you mean the `{ dryRun }` mutation option?",