@openneuro/server 4.20.5 → 4.20.6-alpha.3

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 (185) hide show
  1. package/package.json +4 -6
  2. package/src/__mocks__/{config.js → config.ts} +5 -5
  3. package/src/app.ts +32 -31
  4. package/src/cache/item.ts +6 -7
  5. package/src/cache/types.ts +8 -8
  6. package/src/{config.js → config.ts} +6 -6
  7. package/src/datalad/__tests__/changelog.spec.ts +83 -0
  8. package/src/datalad/__tests__/dataset.spec.ts +109 -0
  9. package/src/datalad/__tests__/description.spec.ts +141 -0
  10. package/src/datalad/__tests__/files.spec.ts +77 -0
  11. package/src/datalad/__tests__/pagination.spec.ts +136 -0
  12. package/src/datalad/__tests__/{snapshots.spec.js → snapshots.spec.ts} +17 -17
  13. package/src/datalad/{analytics.js → analytics.ts} +4 -4
  14. package/src/datalad/{changelog.js → changelog.ts} +17 -14
  15. package/src/datalad/{dataset.js → dataset.ts} +95 -93
  16. package/src/datalad/{description.js → description.ts} +37 -37
  17. package/src/datalad/draft.ts +38 -0
  18. package/src/datalad/files.ts +26 -20
  19. package/src/datalad/{pagination.js → pagination.ts} +47 -47
  20. package/src/datalad/{readme.js → readme.ts} +13 -11
  21. package/src/datalad/{reexporter.js → reexporter.ts} +4 -4
  22. package/src/datalad/{snapshots.js → snapshots.ts} +56 -62
  23. package/src/datalad/{upload.js → upload.ts} +7 -5
  24. package/src/elasticsearch/elastic-client.ts +11 -0
  25. package/src/elasticsearch/reindex-dataset.ts +7 -7
  26. package/src/graphql/__tests__/__snapshots__/permissions.spec.ts.snap +5 -0
  27. package/src/graphql/__tests__/{comment.spec.js → comment.spec.ts} +17 -17
  28. package/src/graphql/__tests__/permissions.spec.ts +113 -0
  29. package/src/graphql/{permissions.js → permissions.ts} +14 -14
  30. package/src/graphql/resolvers/__tests__/brainlife.spec.ts +11 -11
  31. package/src/graphql/resolvers/__tests__/{dataset-search.spec.js → dataset-search.spec.ts} +25 -23
  32. package/src/graphql/resolvers/__tests__/dataset.spec.ts +175 -0
  33. package/src/graphql/resolvers/__tests__/derivatives.spec.ts +19 -19
  34. package/src/graphql/resolvers/__tests__/importRemoteDataset.spec.ts +20 -20
  35. package/src/graphql/resolvers/__tests__/permssions.spec.ts +35 -0
  36. package/src/graphql/resolvers/__tests__/snapshots.spec.ts +59 -0
  37. package/src/graphql/resolvers/__tests__/user.spec.ts +18 -0
  38. package/src/graphql/resolvers/brainlife.ts +4 -4
  39. package/src/graphql/resolvers/cache.ts +4 -4
  40. package/src/graphql/resolvers/{comment.js → comment.ts} +16 -16
  41. package/src/graphql/resolvers/{dataset-search.js → dataset-search.ts} +45 -43
  42. package/src/graphql/resolvers/{dataset.js → dataset.ts} +38 -52
  43. package/src/graphql/resolvers/datasetType.ts +3 -3
  44. package/src/graphql/resolvers/derivatives.ts +11 -11
  45. package/src/graphql/resolvers/description.ts +18 -0
  46. package/src/graphql/resolvers/{draft.js → draft.ts} +13 -13
  47. package/src/graphql/resolvers/{flaggedFiles.js → flaggedFiles.ts} +4 -4
  48. package/src/graphql/resolvers/{follow.js → follow.ts} +1 -1
  49. package/src/graphql/resolvers/git.ts +3 -3
  50. package/src/graphql/resolvers/history.ts +13 -0
  51. package/src/graphql/resolvers/importRemoteDataset.ts +12 -11
  52. package/src/graphql/resolvers/index.ts +25 -0
  53. package/src/graphql/resolvers/{issues.js → issues.ts} +9 -9
  54. package/src/graphql/resolvers/metadata.ts +8 -8
  55. package/src/graphql/resolvers/{mutation.js → mutation.ts} +26 -26
  56. package/src/graphql/resolvers/{newsletter.js → newsletter.ts} +2 -2
  57. package/src/graphql/resolvers/permissions.ts +15 -21
  58. package/src/graphql/resolvers/publish.ts +17 -0
  59. package/src/graphql/resolvers/query.ts +21 -0
  60. package/src/graphql/resolvers/{readme.js → readme.ts} +3 -3
  61. package/src/graphql/resolvers/{reexporter.js → reexporter.ts} +2 -2
  62. package/src/graphql/resolvers/relation.ts +5 -5
  63. package/src/graphql/resolvers/{reset.js → reset.ts} +2 -2
  64. package/src/graphql/resolvers/reviewer.ts +4 -4
  65. package/src/graphql/resolvers/{snapshots.js → snapshots.ts} +49 -49
  66. package/src/graphql/resolvers/{stars.js → stars.ts} +1 -1
  67. package/src/graphql/resolvers/summary.ts +3 -3
  68. package/src/graphql/resolvers/{upload.js → upload.ts} +5 -5
  69. package/src/graphql/resolvers/{user.js → user.ts} +16 -18
  70. package/src/graphql/resolvers/{validation.js → validation.ts} +12 -14
  71. package/src/graphql/{schema.js → schema.ts} +4 -6
  72. package/src/graphql/utils/{file.js → file.ts} +2 -2
  73. package/src/handlers/{comments.js → comments.ts} +11 -11
  74. package/src/handlers/{config.js → config.ts} +1 -1
  75. package/src/handlers/{datalad.js → datalad.ts} +22 -22
  76. package/src/handlers/{doi.js → doi.ts} +6 -6
  77. package/src/handlers/reviewer.ts +6 -6
  78. package/src/handlers/{sitemap.js → sitemap.ts} +19 -19
  79. package/src/handlers/stars.ts +11 -10
  80. package/src/handlers/{subscriptions.js → subscriptions.ts} +17 -16
  81. package/src/handlers/{users.js → users.ts} +3 -3
  82. package/src/libs/__tests__/apikey.spec.ts +25 -0
  83. package/src/libs/__tests__/datalad-service.spec.ts +27 -0
  84. package/src/libs/__tests__/{dataset.spec.js → dataset.spec.ts} +9 -9
  85. package/src/libs/{apikey.js → apikey.ts} +5 -5
  86. package/src/libs/authentication/__tests__/jwt.spec.ts +59 -0
  87. package/src/libs/authentication/{crypto.js → crypto.ts} +16 -16
  88. package/src/libs/authentication/google.ts +18 -0
  89. package/src/libs/authentication/jwt.ts +40 -33
  90. package/src/libs/authentication/{orcid.js → orcid.ts} +11 -11
  91. package/src/libs/authentication/{passport.js → passport.ts} +45 -30
  92. package/src/libs/authentication/{states.js → states.ts} +17 -20
  93. package/src/libs/{counter.js → counter.ts} +1 -1
  94. package/src/libs/{datalad-service.js → datalad-service.ts} +4 -4
  95. package/src/libs/dataset.ts +9 -0
  96. package/src/libs/doi/__tests__/__snapshots__/doi.spec.ts.snap +17 -0
  97. package/src/libs/doi/__tests__/doi.spec.ts +25 -0
  98. package/src/libs/doi/__tests__/normalize.spec.ts +19 -19
  99. package/src/libs/doi/{index.js → index.ts} +27 -21
  100. package/src/libs/doi/normalize.ts +2 -2
  101. package/src/libs/email/__tests__/index.spec.ts +14 -14
  102. package/src/libs/email/index.ts +4 -4
  103. package/src/libs/email/templates/__tests__/comment-created.spec.ts +12 -12
  104. package/src/libs/email/templates/__tests__/dataset-deleted.spec.ts +6 -6
  105. package/src/libs/email/templates/__tests__/owner-unsubscribed.spec.ts +6 -6
  106. package/src/libs/email/templates/__tests__/snapshot-created.spec.ts +9 -9
  107. package/src/libs/email/templates/__tests__/snapshot-reminder.spec.ts +7 -7
  108. package/src/libs/email/templates/comment-created.ts +2 -1
  109. package/src/libs/email/templates/dataset-deleted.ts +2 -1
  110. package/src/libs/email/templates/dataset-import-failed.ts +2 -1
  111. package/src/libs/email/templates/dataset-imported.ts +2 -1
  112. package/src/libs/email/templates/owner-unsubscribed.ts +2 -1
  113. package/src/libs/email/templates/snapshot-created.ts +2 -1
  114. package/src/libs/email/templates/snapshot-reminder.ts +2 -1
  115. package/src/libs/{notifications.js → notifications.ts} +100 -113
  116. package/src/libs/{orcid.js → orcid.ts} +20 -20
  117. package/src/libs/{redis.js → redis.ts} +6 -6
  118. package/src/models/__tests__/ingestDataset.spec.ts +15 -15
  119. package/src/models/analytics.ts +2 -2
  120. package/src/models/badAnnexObject.ts +6 -6
  121. package/src/models/comment.ts +10 -10
  122. package/src/models/counter.ts +2 -2
  123. package/src/models/dataset.ts +16 -16
  124. package/src/models/deletion.ts +3 -3
  125. package/src/models/deprecatedSnapshot.ts +2 -2
  126. package/src/models/doi.ts +2 -2
  127. package/src/models/file.ts +2 -2
  128. package/src/models/ingestDataset.ts +4 -4
  129. package/src/models/issue.ts +2 -2
  130. package/src/models/key.ts +2 -2
  131. package/src/models/mailgunIdentifier.ts +2 -2
  132. package/src/models/metadata.ts +3 -3
  133. package/src/models/newsletter.ts +3 -3
  134. package/src/models/notification.ts +2 -2
  135. package/src/models/permission.ts +4 -4
  136. package/src/models/reviewer.ts +7 -7
  137. package/src/models/snapshot.ts +2 -2
  138. package/src/models/stars.ts +6 -6
  139. package/src/models/subscription.ts +2 -2
  140. package/src/models/summary.ts +2 -2
  141. package/src/models/upload.ts +3 -3
  142. package/src/models/user.ts +4 -4
  143. package/src/{routes.js → routes.ts} +62 -62
  144. package/src/server.ts +9 -9
  145. package/src/utils/__tests__/datasetOrSnapshot.spec.ts +25 -25
  146. package/src/utils/__tests__/validateUrl.spec.ts +10 -10
  147. package/src/utils/datasetOrSnapshot.ts +2 -2
  148. package/src/utils/validateUrl.ts +1 -1
  149. package/src/datalad/__tests__/changelog.spec.js +0 -82
  150. package/src/datalad/__tests__/dataset.spec.js +0 -109
  151. package/src/datalad/__tests__/description.spec.js +0 -137
  152. package/src/datalad/__tests__/files.spec.js +0 -75
  153. package/src/datalad/__tests__/pagination.spec.js +0 -136
  154. package/src/datalad/draft.js +0 -37
  155. package/src/elasticsearch/elastic-client.js +0 -11
  156. package/src/graphql/__tests__/permissions.spec.js +0 -107
  157. package/src/graphql/pubsub.js +0 -5
  158. package/src/graphql/resolvers/__tests__/dataset.spec.js +0 -175
  159. package/src/graphql/resolvers/__tests__/permssions.spec.js +0 -34
  160. package/src/graphql/resolvers/__tests__/snapshots.spec.js +0 -58
  161. package/src/graphql/resolvers/__tests__/user.spec.js +0 -17
  162. package/src/graphql/resolvers/description.js +0 -29
  163. package/src/graphql/resolvers/history.js +0 -11
  164. package/src/graphql/resolvers/index.js +0 -25
  165. package/src/graphql/resolvers/publish.js +0 -17
  166. package/src/graphql/resolvers/query.js +0 -21
  167. package/src/graphql/resolvers/subscriptions.js +0 -81
  168. package/src/graphql/utils/publish-draft-update.js +0 -13
  169. package/src/libs/__tests__/apikey.spec.js +0 -24
  170. package/src/libs/__tests__/datalad-service.spec.js +0 -26
  171. package/src/libs/authentication/__tests__/jwt.spec.js +0 -23
  172. package/src/libs/authentication/globus.js +0 -11
  173. package/src/libs/authentication/google.js +0 -19
  174. package/src/libs/bidsId.js +0 -68
  175. package/src/libs/dataset.js +0 -9
  176. package/src/libs/doi/__tests__/doi.spec.js +0 -24
  177. package/src/libs/redis-pubsub.js +0 -5
  178. package/src/libs/request.js +0 -155
  179. package/src/libs/scitran.js +0 -25
  180. package/src/libs/subscription-server.js +0 -20
  181. package/src/libs/testing-utils.js +0 -17
  182. package/src/persistent/datasets/.gitignore +0 -3
  183. package/src/persistent/temp/.gitignore +0 -3
  184. /package/src/libs/__mocks__/{notifications.js → notifications.ts} +0 -0
  185. /package/src/libs/authentication/{verifyUser.js → verifyUser.ts} +0 -0
@@ -1,137 +0,0 @@
1
- import {
2
- getDescriptionObject,
3
- repairDescriptionTypes,
4
- appendSeniorAuthor,
5
- } from '../description.js'
6
-
7
- // Mock requests to Datalad service
8
- vi.mock('ioredis')
9
- vi.mock('../../config.js')
10
-
11
- describe('datalad dataset descriptions', () => {
12
- describe('appendSeniorAuthor', () => {
13
- it('returns author out of several', () => {
14
- expect(
15
- appendSeniorAuthor({
16
- Authors: ['A. Bee', 'C. Dee', 'E. Eff'],
17
- Name: 'test dataset',
18
- }),
19
- ).toHaveProperty('SeniorAuthor', 'E. Eff')
20
- })
21
- it('returns a description when no Authors array is provided', () => {
22
- expect(
23
- appendSeniorAuthor({ Authors: null, Name: 'test dataset' }),
24
- ).toHaveProperty('Name', 'test dataset')
25
- })
26
- it('returns a description when no Authors array is empty', () => {
27
- expect(
28
- appendSeniorAuthor({ Authors: [], Name: 'test dataset' }),
29
- ).toHaveProperty('Name', 'test dataset')
30
- })
31
- })
32
- describe('repairDescriptionTypes', () => {
33
- it('converts strings to one element arrays for array fields', () => {
34
- const description = {
35
- Authors: 'Not, An Array',
36
- BIDSVersion: '1.2.0',
37
- ReferencesAndLinks: 'https://openneuro.org',
38
- Funding: ['This one', 'is correct'],
39
- EthicsApprovals: 'Also, Not, Array',
40
- }
41
- const repaired = repairDescriptionTypes(description)
42
- // Check for discarded fields
43
- expect(repaired.BIDSVersion).toBe(description.BIDSVersion)
44
- // Check for extra fields
45
- expect(repaired.DatasetDOI).toBe(undefined)
46
- // Test each repaired field for type correct value
47
- expect(Array.isArray(repaired.Authors)).toBe(true)
48
- expect(Array.isArray(repaired.ReferencesAndLinks)).toBe(true)
49
- expect(Array.isArray(repaired.Funding)).toBe(true)
50
- expect(Array.isArray(repaired.EthicsApprovals)).toBe(true)
51
- })
52
- it('converts any invalid value to string values for string fields', () => {
53
- const description = {
54
- BIDSVersion: '1.2.0',
55
- Name: 1.5,
56
- DatasetDOI: ['Should', 'not', 'be', 'an', 'array'],
57
- Acknowledgements: ['Should not be an array'],
58
- HowToAcknowledge: Symbol(), // This can't serialize but just in case
59
- }
60
- const repaired = repairDescriptionTypes(description)
61
- // Check for discarded fields
62
- expect(repaired.BIDSVersion).toBe(description.BIDSVersion)
63
- // Check for extra fields
64
- expect(repaired.Authors).toBe(undefined)
65
- // Check converted types
66
- expect(typeof repaired.Name).toBe('string')
67
- expect(typeof repaired.DatasetDOI).toBe('string')
68
- expect(typeof repaired.Acknowledgements).toBe('string')
69
- expect(typeof repaired.HowToAcknowledge).toBe('string')
70
- })
71
- it('returns correct types for empty strings', () => {
72
- const description = {
73
- Name: 'Classification learning',
74
- License:
75
- 'This dataset is made available under the Public Domain Dedication and License \nv1.0, whose full text can be found at \nhttp://www.opendatacommons.org/licenses/pddl/1.0/. \nWe hope that all users will follow the ODC Attribution/Share-Alike \nCommunity Norms (http://www.opendatacommons.org/norms/odc-by-sa/); \nin particular, while not legally required, we hope that all users \nof the data will acknowledge the OpenfMRI project and NSF Grant \nOCI-1131441 (R. Poldrack, PI) in any publications.',
76
- Authors: '',
77
- Acknowledgements: '',
78
- HowToAcknowledge: '',
79
- Funding: '',
80
- ReferencesAndLinks: '',
81
- DatasetDOI: '',
82
- BIDSVersion: '1.0.0',
83
- }
84
- const repaired = repairDescriptionTypes(description)
85
- expect(Array.isArray(repaired.Authors)).toBe(true)
86
- expect(Array.isArray(repaired.ReferencesAndLinks)).toBe(true)
87
- expect(Array.isArray(repaired.Funding)).toBe(true)
88
- })
89
- })
90
- describe('getDescriptionObject()', () => {
91
- beforeAll(() => {
92
- global.fetch = vi.fn()
93
- })
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
- )
135
- })
136
- })
137
- })
@@ -1,75 +0,0 @@
1
- import {
2
- encodeFilePath,
3
- decodeFilePath,
4
- fileUrl,
5
- computeTotalSize,
6
- } from '../files'
7
-
8
- vi.mock('ioredis')
9
- vi.mock('../../config.js')
10
-
11
- const filename = 'sub-01/anat/sub-01_T1w.nii.gz'
12
-
13
- const mockRootFiles = [
14
- { filename: 'README' },
15
- { filename: 'dataset_description.json' },
16
- ]
17
- const mockSub01 = [
18
- { filename: 'sub-01/anat/sub-01_T1w.nii.gz' },
19
- { filename: 'sub-01/func/sub-01_task-onebacktask_run-01_bold.nii.gz' },
20
- ]
21
- const mockSub02 = [
22
- { filename: 'sub-02/anat/sub-02_T1w.nii.gz' },
23
- { filename: 'sub-02/func/sub-02_task-onebacktask_run-01_bold.nii.gz' },
24
- ]
25
- const mockSub03 = [
26
- { filename: 'sub-03/anat/sub-03_T1w.nii.gz' },
27
- { filename: 'sub-03/func/sub-03_task-onebacktask_run-01_bold.nii.gz' },
28
- ]
29
- const mockDerivatives = [{ filename: 'derivatives/groundbreaking_output.html' }]
30
- const mockFiles = [
31
- ...mockRootFiles,
32
- ...mockSub01,
33
- ...mockSub02,
34
- ...mockSub03,
35
- ...mockDerivatives,
36
- ]
37
-
38
- describe('datalad files', () => {
39
- describe('encodeFilePath()', () => {
40
- it('should encode a nested path', () => {
41
- expect(encodeFilePath(filename)).toBe('sub-01:anat:sub-01_T1w.nii.gz')
42
- })
43
- })
44
- describe('decodeFilePath()', () => {
45
- it('decodes a file path', () => {
46
- expect(decodeFilePath('sub-01:anat:sub-01_T1w.nii.gz')).toBe(filename)
47
- })
48
- })
49
- describe('fileUrl()', () => {
50
- it('returns a working URL', () => {
51
- expect(fileUrl('ds000001', '', filename)).toBe(
52
- 'http://datalad-0/datasets/ds000001/files/sub-01:anat:sub-01_T1w.nii.gz',
53
- )
54
- })
55
- it('handles path nesting', () => {
56
- expect(fileUrl('ds000001', 'sub-01/anat', 'sub-01_T1w.nii.gz')).toBe(
57
- 'http://datalad-0/datasets/ds000001/files/sub-01:anat:sub-01_T1w.nii.gz',
58
- )
59
- })
60
- })
61
- describe('computeTotalSize()', () => {
62
- it('computes the size correctly', () => {
63
- const mockFileSizes = [
64
- { filename: 'README', size: 234 },
65
- { filename: 'dataset_description.json', size: 432 },
66
- { filename: 'sub-01/anat/sub-01_T1w.nii.gz', size: 10858 },
67
- {
68
- filename: 'sub-01/func/sub-01_task-onebacktask_run-01_bold.nii.gz',
69
- size: 1945682,
70
- },
71
- ]
72
- expect(computeTotalSize(mockFileSizes)).toBe(1957206)
73
- })
74
- })
75
- })
@@ -1,136 +0,0 @@
1
- import { vi } from 'vitest'
2
- vi.mock('ioredis')
3
- import * as pagination from '../pagination.js'
4
- import { connect, Types } from 'mongoose'
5
- import Dataset from '../../models/dataset'
6
- const ObjectID = Types.ObjectId
7
-
8
- const base64 = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/
9
-
10
- describe('pagination model operations', () => {
11
- describe('enumToMongoSort()', () => {
12
- it('should convert enum strings to -1 or 1 values', () => {
13
- expect(
14
- pagination.enumToMongoSort({
15
- created: 'descending',
16
- name: 'ascending',
17
- }),
18
- ).toEqual({ created: -1, name: 1 })
19
- })
20
- })
21
- describe('apiCursor()', () => {
22
- it('returns base64 string', () => {
23
- expect(pagination.apiCursor(new ObjectID(5))).toMatch(base64)
24
- })
25
- })
26
- describe('applyCursorToEdges()', () => {
27
- it('returns the correct shape matching Relay connections', () => {
28
- const res = pagination.applyCursorToEdges(
29
- [{ _id: '123' }, { _id: '234' }, { _id: '345' }],
30
- 0,
31
- )
32
- expect(res).toEqual([
33
- { cursor: 'eyJvZmZzZXQiOjB9', node: { _id: '123' } },
34
- { cursor: 'eyJvZmZzZXQiOjF9', node: { _id: '234' } },
35
- { cursor: 'eyJvZmZzZXQiOjJ9', node: { _id: '345' } },
36
- ])
37
- })
38
- })
39
- describe('datasetsConnection()', () => {
40
- beforeAll(async () => {
41
- await connect(globalThis.__MONGO_URI__)
42
- const ds = new Dataset({
43
- _id: new ObjectID('5bef51a1ed211400c08e5524'),
44
- id: 'ds001001',
45
- created: new Date('2018-11-16T23:24:17.203Z'),
46
- modified: new Date('2018-11-16T23:24:25.050Z'),
47
- uploader: 'f8d5a57c-879a-40e6-b151-e34c4a28ff70',
48
- revision: '262a8e610e32b5766cbf669acc71911c1ece7126',
49
- })
50
- await ds.save()
51
- })
52
- it('returns a connection shaped result', async () => {
53
- const res = await pagination.datasetsConnection({
54
- orderBy: { created: 'ascending' },
55
- limit: 5,
56
- first: 10,
57
- })([])
58
- expect(res).toHaveProperty('pageInfo')
59
- expect(res).toHaveProperty('edges')
60
- })
61
- })
62
- describe('maxLimit()', () => {
63
- it('should be within range 1-100', () => {
64
- expect(pagination.maxLimit(0)).toBe(1)
65
- expect(pagination.maxLimit(101)).toBe(100)
66
- })
67
- it('does not error with negative values', () => {
68
- expect(pagination.maxLimit(-10)).toBe(1)
69
- })
70
- })
71
- describe('sortAggregate()', () => {
72
- it('should return natural sort for orderBy: created', () => {
73
- expect(
74
- pagination.sortAggregate({ orderBy: { created: 'ascending' } }),
75
- ).toEqual([{ $sort: { _id: 1 } }])
76
- })
77
- it('does not throw an error with no orderBy', () => {
78
- expect(pagination.sortAggregate({})).toEqual([])
79
- })
80
- it('should return -1 for descending sorts', () => {
81
- expect(
82
- pagination.sortAggregate({ orderBy: { created: 'descending' } }),
83
- ).toEqual([{ $sort: { _id: -1 } }])
84
- })
85
- it('includes "name" for name sorts', () => {
86
- expect(
87
- pagination.sortAggregate({ orderBy: { name: 'descending' } }),
88
- ).toEqual([{ $sort: { name: -1 } }])
89
- })
90
- it('returns a lookup and count stage for stars', () => {
91
- const agg = pagination.sortAggregate({ orderBy: { stars: 'ascending' } })
92
- expect(agg[0]).toHaveProperty('$lookup')
93
- expect(agg[1]).toHaveProperty('$addFields')
94
- // Ends with count sort
95
- expect(agg.slice(-1)).toEqual([{ $sort: { starsCount: 1 } }])
96
- })
97
- it('returns a lookup and count stage for subscriptions', () => {
98
- const agg = pagination.sortAggregate({
99
- orderBy: { subscriptions: 'descending' },
100
- })
101
- expect(agg[0]).toHaveProperty('$lookup')
102
- expect(agg[1]).toHaveProperty('$addFields')
103
- // Ends with count sort
104
- expect(agg.slice(-1)).toEqual([{ $sort: { subscriptionsCount: -1 } }])
105
- })
106
- it('returns a lookup and no count stage for downloads', () => {
107
- const agg = pagination.sortAggregate({
108
- orderBy: { downloads: 'ascending' },
109
- })
110
- // Ends with count sort
111
- expect(agg.slice(-1)).toEqual([{ $sort: { downloads: 1 } }])
112
- })
113
- it('returns a lookup and no count stage for views', () => {
114
- const agg = pagination.sortAggregate({
115
- orderBy: { views: 'ascending' },
116
- })
117
- // Ends with count sort
118
- expect(agg.slice(-1)).toEqual([{ $sort: { views: 1 } }])
119
- })
120
- it('does not explode with all sorts', () => {
121
- const agg = pagination.sortAggregate({
122
- orderBy: {
123
- created: 'ascending',
124
- name: 'ascending',
125
- uploader: 'ascending',
126
- stars: 'ascending',
127
- downloads: 'ascending',
128
- subscriptions: 'ascending',
129
- },
130
- })
131
- expect(agg).toHaveLength(6)
132
- // Final stage should always be a sort
133
- expect(agg.slice(-1)[0]).toHaveProperty('$sort')
134
- })
135
- })
136
- })
@@ -1,37 +0,0 @@
1
- /**
2
- * Manage serving a Draft object based on DataLad working trees
3
- */
4
- import request from 'superagent'
5
- import Dataset from '../models/dataset'
6
- import publishDraftUpdate from '../graphql/utils/publish-draft-update.js'
7
- import { getDatasetWorker } from '../libs/datalad-service'
8
-
9
- export const getDraftRevision = async datasetId => {
10
- const draftUrl = `http://${getDatasetWorker(
11
- datasetId,
12
- )}/datasets/${datasetId}/draft`
13
- const response = await fetch(draftUrl)
14
- const { hexsha } = await response.json()
15
- return hexsha
16
- }
17
-
18
- export const updateDatasetRevision = (datasetId, gitRef) => {
19
- /**
20
- * Update the revision modified time in a draft on changes
21
- */
22
- return Dataset.updateOne({ id: datasetId }, { modified: new Date() })
23
- .exec()
24
- .then(() => publishDraftUpdate(datasetId, gitRef))
25
- }
26
-
27
- /**
28
- * Run a git reset on the draft
29
- * @param {string} datasetId Accession number
30
- * @param {string} ref Git hexsha
31
- */
32
- export const resetDraft = (datasetId, ref) => {
33
- const resetUrl = `${getDatasetWorker(
34
- datasetId,
35
- )}/datasets/${datasetId}/reset/${ref}`
36
- return request.post(resetUrl).set('Accept', 'application/json')
37
- }
@@ -1,11 +0,0 @@
1
- import config from '../config.js'
2
- import { Client } from '@elastic/elasticsearch'
3
-
4
- const elasticConfig = {
5
- node: config.elasticsearch.connection || 'http://mock-client',
6
- maxRetries: 3,
7
- }
8
-
9
- export const elasticClient = new Client(elasticConfig)
10
-
11
- export default elasticClient
@@ -1,107 +0,0 @@
1
- import {
2
- datasetReadQuery,
3
- checkPermissionLevel,
4
- states,
5
- checkDatasetWrite,
6
- checkDatasetAdmin,
7
- } from '../permissions'
8
-
9
- vi.mock('ioredis')
10
-
11
- describe('resolver permissions helpers', () => {
12
- describe('datasetReadQuery()', () => {
13
- it('returns public for anonymous users', () => {
14
- expect(datasetReadQuery('ds000001', null, null)).toHaveProperty(
15
- 'public',
16
- true,
17
- )
18
- })
19
- it('returns non-public for admins', () => {
20
- expect(
21
- datasetReadQuery('ds000001', '1234', { admin: true }),
22
- ).not.toHaveProperty('public', true)
23
- })
24
- it('returns public for logged in users', () => {
25
- expect(
26
- datasetReadQuery('ds000001', '1234', { admin: false }),
27
- ).toHaveProperty('public', true)
28
- })
29
- })
30
- describe('checkPermissionLevel(..., READ)', () => {
31
- it('returns false if no permission passed in', () => {
32
- expect(checkPermissionLevel(null, states.READ)).toBe(false)
33
- })
34
- it('returns true for valid read access level', () => {
35
- expect(checkPermissionLevel({ level: 'admin' }, states.READ)).toBe(true)
36
- expect(checkPermissionLevel({ level: 'ro' }, states.READ)).toBe(true)
37
- })
38
- it('returns false if an unexpected level is present', () => {
39
- expect(checkPermissionLevel({ level: 'not-real' }, states.READ)).toBe(
40
- false,
41
- )
42
- })
43
- })
44
- describe('checkPermissionLevel(..., WRITE)', () => {
45
- it('returns false if no permission passed in', () => {
46
- expect(checkPermissionLevel(null, states.WRITE)).toBe(false)
47
- })
48
- it('returns true for admin', () => {
49
- expect(checkPermissionLevel({ level: 'admin' }, states.WRITE)).toBe(true)
50
- })
51
- it('returns false for read only access', () => {
52
- expect(checkPermissionLevel({ level: 'ro' }, states.WRITE)).toBe(false)
53
- })
54
- it('returns false if an unexpected level is present', () => {
55
- expect(checkPermissionLevel({ level: 'not-real' }, states.WRITE)).toBe(
56
- false,
57
- )
58
- })
59
- })
60
- describe('checkDatasetWrite()', () => {
61
- it('resolves to false for anonymous users', () => {
62
- return expect(
63
- checkDatasetWrite('ds000001', null, null, undefined, {
64
- checkExists: false,
65
- }),
66
- ).rejects.toThrowErrorMatchingSnapshot()
67
- })
68
- it('resolves to true for admins', () => {
69
- return expect(
70
- checkDatasetWrite('ds000001', '1234', { admin: true }, undefined, {
71
- checkExists: false,
72
- }),
73
- ).resolves.toBe(true)
74
- })
75
- })
76
- describe('checkPermissionLevel(..., ADMIN)', () => {
77
- it('returns false if no permission passed in', () => {
78
- expect(checkPermissionLevel(null, states.ADMIN)).toBe(false)
79
- })
80
- it('returns true for admin', () => {
81
- expect(checkPermissionLevel({ level: 'admin' }, states.ADMIN)).toBe(true)
82
- })
83
- it('returns false for read write access', () => {
84
- expect(checkPermissionLevel({ level: 'rw' }, states.ADMIN)).toBe(false)
85
- })
86
- it('returns false for read only access', () => {
87
- expect(checkPermissionLevel({ level: 'ro' }, states.ADMIN)).toBe(false)
88
- })
89
- it('returns false if an unexpected level is present', () => {
90
- expect(checkPermissionLevel({ level: 'not-real' }, states.ADMIN)).toBe(
91
- false,
92
- )
93
- })
94
- })
95
- describe('checkDatasetAdmin()', () => {
96
- it('resolves to false for anonymous users', () => {
97
- return expect(
98
- checkDatasetAdmin('ds000001', null, null, false),
99
- ).rejects.toThrowErrorMatchingSnapshot()
100
- })
101
- it('resolves to true for admins', () => {
102
- return expect(
103
- checkDatasetAdmin('ds000001', '1234', { admin: true }, false),
104
- ).resolves.toBe(true)
105
- })
106
- })
107
- })
@@ -1,5 +0,0 @@
1
- async function* asyncIterator(_) {
2
- yield null
3
- }
4
-
5
- export default { publish: (_, __) => {}, asyncIterator }
@@ -1,175 +0,0 @@
1
- import { vi } from 'vitest'
2
- import { connect } from 'mongoose'
3
- import request from 'superagent'
4
- import * as ds from '../dataset'
5
-
6
- vi.mock('superagent')
7
- vi.mock('ioredis')
8
- vi.mock('../../../config.js')
9
- vi.mock('../../../libs/notifications.js')
10
-
11
- describe('dataset resolvers', () => {
12
- beforeAll(() => {
13
- connect(globalThis.__MONGO_URI__)
14
- })
15
- describe('createDataset()', () => {
16
- it('createDataset mutation succeeds', async () => {
17
- const { id: dsId } = await ds.createDataset(
18
- null,
19
- { affirmedDefaced: true, affirmedConsent: false },
20
- { user: '123456', userInfo: {} },
21
- )
22
- expect(dsId).toEqual(expect.stringMatching(/^ds[0-9]{6}$/))
23
- })
24
- })
25
- describe('snapshotCreationComparison()', () => {
26
- it('sorts array of objects by the "created" and "tag" properties', () => {
27
- const testArray = [
28
- { id: 2, created: new Date('2018-11-20T00:05:43.473Z'), tag: '1.0.0' },
29
- { id: 1, created: new Date('2018-11-19T00:05:43.473Z'), tag: '1.0.1' },
30
- { id: 3, created: new Date('2018-11-23T00:05:43.473Z'), tag: '1.0.2' },
31
- { id: 5, created: new Date('2018-11-23T00:05:43.473Z'), tag: '1.0.10' },
32
- { id: 4, created: new Date('2018-11-23T00:05:43.473Z'), tag: '1.0.3' },
33
- ]
34
- const sorted = testArray.sort(ds.snapshotCreationComparison)
35
- expect(sorted[0].id).toBe(2)
36
- expect(sorted[1].id).toBe(1)
37
- expect(sorted[2].id).toBe(3)
38
- expect(sorted[3].id).toBe(4)
39
- expect(sorted[4].id).toBe(5)
40
- })
41
- it('sorts array of objects by the "created" property as strings', () => {
42
- const testArray = [
43
- { id: 2, created: '2018-11-20T00:05:43.473Z' },
44
- { id: 1, created: '2018-11-19T00:05:43.473Z' },
45
- { id: 3, created: '2018-11-23T00:05:43.473Z' },
46
- ]
47
- const sorted = testArray.sort(ds.snapshotCreationComparison)
48
- expect(sorted[0].id).toBe(1)
49
- expect(sorted[1].id).toBe(2)
50
- expect(sorted[2].id).toBe(3)
51
- })
52
- it('sorts non-semver tags mixed with semver tags', () => {
53
- const testArray = [
54
- { id: 2, created: new Date('2018-11-19T00:05:43.473Z'), tag: '1.0.2' },
55
- {
56
- id: 1,
57
- created: new Date('2018-11-19T00:05:43.473Z'),
58
- tag: '57fed018cce88d000ac1757f',
59
- },
60
- { id: 3, created: new Date('2018-11-19T00:05:43.473Z'), tag: '1.0.1' },
61
- ]
62
- const sorted = testArray.sort(ds.snapshotCreationComparison)
63
- expect(sorted[0].id).toBe(2)
64
- expect(sorted[1].id).toBe(1)
65
- expect(sorted[2].id).toBe(3)
66
- })
67
- it('sorts snapshots with only non-semver tags', () => {
68
- const testArray = [
69
- {
70
- id: 2,
71
- created: new Date('2018-11-19T00:05:43.473Z'),
72
- tag: '00001',
73
- },
74
- {
75
- id: 1,
76
- created: new Date('2018-11-19T00:05:43.473Z'),
77
- tag: '57fed018cce88d000ac1757f',
78
- },
79
- {
80
- id: 3,
81
- created: new Date('2018-11-19T00:05:43.473Z'),
82
- tag: '57fed018cce88d000ac1757f',
83
- },
84
- ]
85
- const sorted = testArray.sort(ds.snapshotCreationComparison)
86
- expect(sorted[0].id).toBe(2)
87
- expect(sorted[1].id).toBe(1)
88
- expect(sorted[2].id).toBe(3)
89
- })
90
- it('sorts very similar creation times by semver order', () => {
91
- const testSnapshots = [
92
- {
93
- id: 'ds002680:1.0.0',
94
- created: '2020-04-03T23:19:56.000Z',
95
- tag: '1.0.0',
96
- },
97
- {
98
- id: 'ds002680:1.2.0',
99
- created: '2021-10-19T16:26:43.000Z',
100
- tag: '1.2.0',
101
- },
102
- {
103
- id: 'ds002680:1.1.0',
104
- created: '2021-10-19T16:26:44.000Z',
105
- tag: '1.1.0',
106
- },
107
- ]
108
- const sorted = testSnapshots.sort(ds.snapshotCreationComparison)
109
- expect(sorted[0].id).toBe('ds002680:1.0.0')
110
- expect(sorted[1].id).toBe('ds002680:1.1.0')
111
- expect(sorted[2].id).toBe('ds002680:1.2.0')
112
- })
113
- it('sorts 000002 (legacy snapshots) before 1.0.1 (current format)', () => {
114
- const testSnapshots = [
115
- {
116
- id: 'ds000247:00002',
117
- created: '2018-07-18T02:27:39.000Z',
118
- tag: '00002',
119
- },
120
- {
121
- id: 'ds000247:00001',
122
- created: '2018-07-18T02:35:37.000Z',
123
- tag: '00001',
124
- },
125
- {
126
- id: 'ds000247:1.0.0',
127
- created: '2021-07-05T15:58:18.000Z',
128
- tag: '1.0.0',
129
- },
130
- {
131
- id: 'ds000247:1.0.1',
132
- created: '2021-08-25T23:37:53.000Z',
133
- tag: '1.0.1',
134
- },
135
- ]
136
- const sorted = testSnapshots.sort(ds.snapshotCreationComparison)
137
- expect(sorted[0].id).toBe('ds000247:00002')
138
- expect(sorted[1].id).toBe('ds000247:00001')
139
- expect(sorted[2].id).toBe('ds000247:1.0.0')
140
- expect(sorted[3].id).toBe('ds000247:1.0.1')
141
- })
142
- })
143
- describe('deleteFiles', () => {
144
- beforeEach(() => {
145
- request.post.mockClear()
146
- })
147
- it('makes correct delete call to datalad', () => {
148
- // capture and check datalad delete request
149
- request.del = url => ({
150
- set: (header1, headerValue1) => ({
151
- set: () => ({
152
- send: ({ filenames }) => {
153
- expect(url).toEqual('http://datalad-0/datasets/ds999999/files')
154
- expect(filenames).toEqual([':sub-99'])
155
- expect(header1).toEqual('Cookie')
156
- expect(headerValue1).toMatch(/^accessToken=/)
157
- },
158
- }),
159
- }),
160
- })
161
-
162
- return ds.deleteFiles(
163
- null,
164
- { datasetId: 'ds999999', files: [{ path: '/sub-99' }] },
165
- {
166
- user: 'a_user_id',
167
- userInfo: {
168
- // bypass permission checks
169
- admin: true,
170
- },
171
- },
172
- )
173
- })
174
- })
175
- })