@openneuro/server 4.14.3 → 4.15.0-alpha.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openneuro/server",
3
- "version": "4.14.3",
3
+ "version": "4.15.0-alpha.0",
4
4
  "description": "Core service for the OpenNeuro platform.",
5
5
  "license": "MIT",
6
6
  "main": "src/server.js",
@@ -17,7 +17,7 @@
17
17
  "dependencies": {
18
18
  "@apollo/client": "3.7.2",
19
19
  "@elastic/elasticsearch": "7.15.0",
20
- "@openneuro/search": "^4.14.3",
20
+ "@openneuro/search": "^4.15.0-alpha.0",
21
21
  "@passport-next/passport-google-oauth2": "^1.0.0",
22
22
  "@sentry/node": "^4.5.3",
23
23
  "apollo-server": "2.25.4",
@@ -92,5 +92,5 @@
92
92
  "publishConfig": {
93
93
  "access": "public"
94
94
  },
95
- "gitHead": "c6f70fbda1dd828cd801c46db3bb35e1f75b2080"
95
+ "gitHead": "dc1200c0cfaaf00ae5a2103e2ac2759c5355ddad"
96
96
  }
@@ -1,4 +1,3 @@
1
- import request from 'superagent'
2
1
  import {
3
2
  getDescriptionObject,
4
3
  repairDescriptionTypes,
@@ -6,7 +5,6 @@ import {
6
5
  } from '../description.js'
7
6
 
8
7
  // Mock requests to Datalad service
9
- vi.mock('superagent')
10
8
  vi.mock('ioredis')
11
9
  vi.mock('../../config.js')
12
10
 
@@ -89,25 +87,51 @@ describe('datalad dataset descriptions', () => {
89
87
  expect(Array.isArray(repaired.Funding)).toBe(true)
90
88
  })
91
89
  })
92
- it('returns the parsed dataset_description.json object', async () => {
93
- request.post.mockClear()
94
- request.__setMockResponse({
95
- body: { Name: 'Balloon Analog Risk-taking Task' },
96
- type: 'application/json',
90
+ describe('getDescriptionObject()', () => {
91
+ beforeAll(() => {
92
+ global.fetch = vi.fn()
97
93
  })
98
- const description = await getDescriptionObject('ds000001', '1.0.0')
99
- expect(description).toEqual({ Name: 'Balloon Analog Risk-taking Task' })
100
- })
101
- it('handles a corrupted response', async () => {
102
- request.post.mockClear()
103
- request.__setMockResponse({
104
- body: Buffer.from('0x5f3759df', 'hex'),
94
+ it('returns the parsed dataset_description.json object', async () => {
95
+ fetch.mockResolvedValue({
96
+ json: () =>
97
+ Promise.resolve({ Name: 'Balloon Analog Risk-taking Task' }),
98
+ headers: {
99
+ get: () => 'application/json',
100
+ },
101
+ status: 200,
102
+ })
103
+ const description = await getDescriptionObject('ds000001', '1.0.0')
104
+ expect(description).toEqual({ Name: 'Balloon Analog Risk-taking Task' })
105
+ })
106
+ it('handles a corrupted response', async () => {
107
+ global.fetch = vi.fn()
108
+ fetch.mockResolvedValue({
109
+ json: () => Promise.reject('JSON could not be parsed'),
110
+ headers: {
111
+ get: () => 'application/json',
112
+ },
113
+ status: 400,
114
+ })
115
+ await expect(getDescriptionObject('ds000001', '1.0.0')).rejects.toEqual(
116
+ Error(
117
+ 'Backend request failed, dataset_description.json may not exist or may be non-JSON (type: application/json, status: 400)',
118
+ ),
119
+ )
120
+ })
121
+ it('throws an error when nothing is returned', async () => {
122
+ global.fetch = vi.fn()
123
+ fetch.mockResolvedValue({
124
+ json: () => Promise.reject('JSON could not be parsed'),
125
+ headers: {
126
+ get: () => 'application/json',
127
+ },
128
+ status: 404,
129
+ })
130
+ await expect(getDescriptionObject('ds000001', '1.0.0')).rejects.toEqual(
131
+ Error(
132
+ 'Backend request failed, dataset_description.json may not exist or may be non-JSON (type: application/json, status: 404)',
133
+ ),
134
+ )
105
135
  })
106
- const description = await getDescriptionObject('ds000001', '1.0.0')
107
- expect(description).toEqual({ Name: 'ds000001', BIDSVersion: '1.8.0' })
108
- })
109
- it('works without a dataset_description.json being present', async () => {
110
- const description = await getDescriptionObject('ds000001', '1.0.0')
111
- expect(description).toEqual({ Name: 'ds000001', BIDSVersion: '1.8.0' })
112
136
  })
113
137
  })
@@ -16,22 +16,18 @@ import { datasetOrSnapshot } from '../utils/datasetOrSnapshot'
16
16
  * @param {string} datasetId
17
17
  * @returns {Promise<Record<string, unknown>>} Promise resolving to dataset_description.json contents or defaults
18
18
  */
19
- export const getDescriptionObject = (datasetId, revision) => {
20
- const defaultDescription = {
21
- Name: datasetId,
22
- BIDSVersion: '1.8.0',
19
+ export const getDescriptionObject = async (datasetId, revision) => {
20
+ const res = await fetch(
21
+ fileUrl(datasetId, '', 'dataset_description.json', revision),
22
+ )
23
+ const contentType = res.headers.get('content-type')
24
+ if (res.status === 200 && contentType.includes('application/json')) {
25
+ return await res.json()
26
+ } else {
27
+ throw new Error(
28
+ `Backend request failed, dataset_description.json may not exist or may be non-JSON (type: ${contentType}, status: ${res.status})`,
29
+ )
23
30
  }
24
- return request
25
- .get(fileUrl(datasetId, '', 'dataset_description.json', revision))
26
- .then(({ body, type }) => {
27
- // Guard against non-JSON responses
28
- if (type === 'application/json') return body
29
- else throw new Error('dataset_description.json is not JSON')
30
- })
31
- .catch(() => {
32
- // dataset_description does not exist or is not JSON, return default fields
33
- return defaultDescription
34
- })
35
31
  }
36
32
 
37
33
  export const descriptionCacheKey = (datasetId, revision) => {
@@ -111,21 +107,28 @@ export const appendSeniorAuthor = description => {
111
107
  * Get a parsed dataset_description.json
112
108
  * @param {object} obj dataset or snapshot object
113
109
  */
114
- export const description = obj => {
110
+ export const description = async obj => {
115
111
  // Obtain datasetId from Dataset or Snapshot objects
116
112
  const { datasetId, revision } = datasetOrSnapshot(obj)
113
+ // Default fallback if dataset_description.json is not valid or missing
114
+ const defaultDescription = {
115
+ Name: datasetId,
116
+ BIDSVersion: '1.8.0',
117
+ }
117
118
  const cache = new CacheItem(redis, CacheType.datasetDescription, [
118
119
  datasetId,
119
120
  revision.substring(0, 7),
120
121
  ])
121
- return cache
122
- .get(() => {
122
+ try {
123
+ const datasetDescription = await cache.get(() => {
123
124
  return getDescriptionObject(datasetId, revision).then(
124
125
  uncachedDescription => ({ id: revision, ...uncachedDescription }),
125
126
  )
126
127
  })
127
- .then(description => repairDescriptionTypes(description))
128
- .then(description => appendSeniorAuthor(description))
128
+ return appendSeniorAuthor(repairDescriptionTypes(datasetDescription))
129
+ } catch (err) {
130
+ return defaultDescription
131
+ }
129
132
  }
130
133
 
131
134
  export const setDescription = (datasetId, user, descriptionFieldUpdates) => {