@openneuro/server 4.47.5 → 4.47.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openneuro/server",
3
- "version": "4.47.5",
3
+ "version": "4.47.7",
4
4
  "description": "Core service for the OpenNeuro platform.",
5
5
  "license": "MIT",
6
6
  "main": "src/server.js",
@@ -21,7 +21,7 @@
21
21
  "@elastic/elasticsearch": "8.13.1",
22
22
  "@graphql-tools/schema": "^10.0.0",
23
23
  "@keyv/redis": "^4.5.0",
24
- "@openneuro/search": "^4.47.5",
24
+ "@openneuro/search": "^4.47.7",
25
25
  "@sentry/node": "^10.37.0",
26
26
  "@sentry/profiling-node": "^10.37.0",
27
27
  "base64url": "^3.0.0",
@@ -89,5 +89,5 @@
89
89
  "publishConfig": {
90
90
  "access": "public"
91
91
  },
92
- "gitHead": "aad83019a0fe480d21d5997a58c8c7fe6c39cfdb"
92
+ "gitHead": "af17c267fa700dff5b13d1b31aaf4f89c0853165"
93
93
  }
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
1
  import { vi } from "vitest"
3
2
  import request from "superagent"
4
3
  import { createDataset, datasetsFilter, testBlacklist } from "../dataset"
@@ -10,7 +10,8 @@ import {
10
10
  import { MongoMemoryServer } from "mongodb-memory-server"
11
11
  import mongoose, { Types } from "mongoose"
12
12
  import User from "../../../models/user"
13
- import { users } from "../user.js"
13
+ import DatasetEvent from "../../../models/datasetEvents"
14
+ import { notifications, user, users } from "../user.js"
14
15
  import type { GraphQLContext } from "../user.js"
15
16
 
16
17
  vi.mock("ioredis")
@@ -132,6 +133,27 @@ describe("user resolvers", () => {
132
133
  await User.insertMany(testUsersSeedData)
133
134
  })
134
135
 
136
+ describe("user()", () => {
137
+ it("does not crash when userInfo is undefined (anonymous request)", async () => {
138
+ const result = await user(null, { id: "u1" }, {})
139
+ expect(result).not.toBeNull()
140
+ expect(result!.id).toBe("u1")
141
+ // Email should be hidden for anonymous users
142
+ expect(result!.email).toBeUndefined()
143
+ })
144
+
145
+ it("does not crash when context is completely missing", async () => {
146
+ const result = await user(null, { id: "u1" }, undefined)
147
+ expect(result).not.toBeNull()
148
+ expect(result!.id).toBe("u1")
149
+ })
150
+
151
+ it("returns null for non-existent user", async () => {
152
+ const result = await user(null, { id: "nonexistent" }, {})
153
+ expect(result).toBeNull()
154
+ })
155
+ })
156
+
135
157
  describe("users()", () => {
136
158
  it("returns sanitized data for non-admin context", async () => {
137
159
  const result = await users(null, {}, nonAdminContext)
@@ -364,4 +386,57 @@ describe("user resolvers", () => {
364
386
  expect(result.totalCount).toBe(3) // u1, u4, u6 are admins
365
387
  })
366
388
  })
389
+
390
+ describe("notifications()", () => {
391
+ it("returns empty array for reviewer users", async () => {
392
+ const result = await notifications(
393
+ { id: "reviewer" },
394
+ null,
395
+ { userInfo: { reviewer: true, admin: false, blocked: false } },
396
+ )
397
+ expect(result).toEqual([])
398
+ })
399
+
400
+ it("does not crash when userInfo is undefined", async () => {
401
+ await expect(
402
+ notifications({ id: "u1" }, null, { userInfo: undefined }),
403
+ ).rejects.toThrow("Not authorized to view these notifications.")
404
+ })
405
+
406
+ it("returns notifications for a user viewing their own", async () => {
407
+ await DatasetEvent.create({
408
+ id: "event-1",
409
+ datasetId: "ds000001",
410
+ userId: "u1",
411
+ timestamp: new Date(),
412
+ event: {
413
+ type: "contributorCitation",
414
+ datasetId: "ds000001",
415
+ addedBy: "u2",
416
+ targetUserId: "u1",
417
+ contributorData: { name: "Test User" },
418
+ },
419
+ success: true,
420
+ note: "Added as contributor",
421
+ })
422
+ const result = await notifications(
423
+ { id: "u1" },
424
+ null,
425
+ { userInfo: { id: "u1", admin: false, userId: "u1" } },
426
+ )
427
+ expect(result.length).toBe(1)
428
+ expect(result[0].id).toBe("event-1")
429
+ expect(result[0].notificationStatus.status).toBe("UNREAD")
430
+ })
431
+
432
+ it("throws authorization error for regular user viewing another user's notifications", async () => {
433
+ await expect(
434
+ notifications(
435
+ { id: "u1" },
436
+ null,
437
+ { userInfo: { id: "u2", admin: false, userId: "u2" } },
438
+ ),
439
+ ).rejects.toThrow("Not authorized to view these notifications.")
440
+ })
441
+ })
367
442
  })
@@ -34,7 +34,7 @@ export async function user(
34
34
  { id },
35
35
  { userInfo }: { userInfo?: Record<string, unknown> } = {},
36
36
  ): Promise<Partial<GraphQLUserType> | null> {
37
- if (userInfo.reviewer) {
37
+ if (userInfo?.reviewer) {
38
38
  const oneWeekAgo = new Date()
39
39
  oneWeekAgo.setDate(oneWeekAgo.getDate() - 7)
40
40
  return {
@@ -279,7 +279,7 @@ export async function notifications(obj, _, { userInfo }) {
279
279
  const userId = obj.id
280
280
 
281
281
  // Reviewers never have notifications
282
- if (userInfo.reviewer) {
282
+ if (userInfo?.reviewer) {
283
283
  return []
284
284
  }
285
285