index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. 'use strict'
  2. const t = require('tap')
  3. const tnock = require('./fixtures/tnock.js')
  4. const access = require('../index.js')
  5. const REG = 'http://localhost:1337'
  6. const OPTS = {
  7. registry: REG
  8. }
  9. t.test('access public', t => {
  10. tnock(t, REG).post(
  11. '/-/package/%40foo%2Fbar/access', { access: 'public' }
  12. ).reply(200)
  13. return access.public('@foo/bar', OPTS).then(ret => {
  14. t.deepEqual(ret, true, 'request succeeded')
  15. })
  16. })
  17. t.test('access public - failure', t => {
  18. tnock(t, REG).post(
  19. '/-/package/%40foo%2Fbar/access', { access: 'public' }
  20. ).reply(418)
  21. return access.public('@foo/bar', OPTS)
  22. .catch(err => {
  23. t.equals(err.statusCode, 418, 'fails with code from registry')
  24. })
  25. })
  26. t.test('access restricted', t => {
  27. tnock(t, REG).post(
  28. '/-/package/%40foo%2Fbar/access', { access: 'restricted' }
  29. ).reply(200)
  30. return access.restricted('@foo/bar', OPTS).then(ret => {
  31. t.deepEqual(ret, true, 'request succeeded')
  32. })
  33. })
  34. t.test('access restricted - failure', t => {
  35. tnock(t, REG).post(
  36. '/-/package/%40foo%2Fbar/access', { access: 'restricted' }
  37. ).reply(418)
  38. return access.restricted('@foo/bar', OPTS)
  39. .catch(err => {
  40. t.equals(err.statusCode, 418, 'fails with code from registry')
  41. })
  42. })
  43. t.test('access 2fa-required', t => {
  44. tnock(t, REG).post('/-/package/%40foo%2Fbar/access', {
  45. publish_requires_tfa: true
  46. }).reply(200, { ok: true })
  47. return access.tfaRequired('@foo/bar', OPTS).then(ret => {
  48. t.deepEqual(ret, true, 'request succeeded')
  49. })
  50. })
  51. t.test('access 2fa-not-required', t => {
  52. tnock(t, REG).post('/-/package/%40foo%2Fbar/access', {
  53. publish_requires_tfa: false
  54. }).reply(200, { ok: true })
  55. return access.tfaNotRequired('@foo/bar', OPTS).then(ret => {
  56. t.deepEqual(ret, true, 'request succeeded')
  57. })
  58. })
  59. t.test('access grant basic read-write', t => {
  60. tnock(t, REG).put('/-/team/myorg/myteam/package', {
  61. package: '@foo/bar',
  62. permissions: 'read-write'
  63. }).reply(201)
  64. return access.grant(
  65. '@foo/bar', 'myorg:myteam', 'read-write', OPTS
  66. ).then(ret => {
  67. t.deepEqual(ret, true, 'request succeeded')
  68. })
  69. })
  70. t.test('access grant basic read-only', t => {
  71. tnock(t, REG).put('/-/team/myorg/myteam/package', {
  72. package: '@foo/bar',
  73. permissions: 'read-only'
  74. }).reply(201)
  75. return access.grant(
  76. '@foo/bar', 'myorg:myteam', 'read-only', OPTS
  77. ).then(ret => {
  78. t.deepEqual(ret, true, 'request succeeded')
  79. })
  80. })
  81. t.test('access grant bad perm', t => {
  82. return access.grant(
  83. '@foo/bar', 'myorg:myteam', 'unknown', OPTS
  84. ).then(ret => {
  85. throw new Error('should not have succeeded')
  86. }, err => {
  87. t.match(
  88. err.message,
  89. /must be.*read-write.*read-only/,
  90. 'only read-write and read-only are accepted'
  91. )
  92. })
  93. })
  94. t.test('access grant no entity', t => {
  95. return access.grant(
  96. '@foo/bar', undefined, 'read-write', OPTS
  97. ).then(ret => {
  98. throw new Error('should not have succeeded')
  99. }, err => {
  100. t.match(
  101. err.message,
  102. /Expected string/,
  103. 'passing undefined entity gives useful error'
  104. )
  105. })
  106. })
  107. t.test('access grant basic unscoped', t => {
  108. tnock(t, REG).put('/-/team/myorg/myteam/package', {
  109. package: 'bar',
  110. permissions: 'read-write'
  111. }).reply(201)
  112. return access.grant(
  113. 'bar', 'myorg:myteam', 'read-write', OPTS
  114. ).then(ret => {
  115. t.deepEqual(ret, true, 'request succeeded')
  116. })
  117. })
  118. t.test('access grant no opts passed', t => {
  119. // NOTE: mocking real url, because no opts variable means `registry` value
  120. // will be defauled to real registry url
  121. tnock(t, 'https://registry.npmjs.org')
  122. .put('/-/team/myorg/myteam/package', {
  123. package: 'bar',
  124. permissions: 'read-write'
  125. })
  126. .reply(201)
  127. return access.grant('bar', 'myorg:myteam', 'read-write')
  128. .then(ret => {
  129. t.equals(ret, true, 'request succeeded')
  130. })
  131. })
  132. t.test('access revoke basic', t => {
  133. tnock(t, REG).delete('/-/team/myorg/myteam/package', {
  134. package: '@foo/bar'
  135. }).reply(200)
  136. return access.revoke('@foo/bar', 'myorg:myteam', OPTS).then(ret => {
  137. t.deepEqual(ret, true, 'request succeeded')
  138. })
  139. })
  140. t.test('access revoke basic unscoped', t => {
  141. tnock(t, REG).delete('/-/team/myorg/myteam/package', {
  142. package: 'bar'
  143. }).reply(200, { accessChanged: true })
  144. return access.revoke('bar', 'myorg:myteam', OPTS).then(ret => {
  145. t.deepEqual(ret, true, 'request succeeded')
  146. })
  147. })
  148. t.test('access revoke no opts passed', t => {
  149. // NOTE: mocking real url, because no opts variable means `registry` value
  150. // will be defauled to real registry url
  151. tnock(t, 'https://registry.npmjs.org')
  152. .delete('/-/team/myorg/myteam/package', {
  153. package: 'bar'
  154. })
  155. .reply(201)
  156. return access.revoke('bar', 'myorg:myteam')
  157. .then(ret => {
  158. t.equals(ret, true, 'request succeeded')
  159. })
  160. })
  161. t.test('ls-packages on team', t => {
  162. const serverPackages = {
  163. '@foo/bar': 'write',
  164. '@foo/util': 'read',
  165. '@foo/other': 'shrödinger'
  166. }
  167. const clientPackages = {
  168. '@foo/bar': 'read-write',
  169. '@foo/util': 'read-only',
  170. '@foo/other': 'shrödinger'
  171. }
  172. tnock(t, REG).get(
  173. '/-/team/myorg/myteam/package?format=cli'
  174. ).reply(200, serverPackages)
  175. return access.lsPackages('myorg:myteam', OPTS).then(data => {
  176. t.deepEqual(data, clientPackages, 'got client package info')
  177. })
  178. })
  179. t.test('ls-packages on org', t => {
  180. const serverPackages = {
  181. '@foo/bar': 'write',
  182. '@foo/util': 'read',
  183. '@foo/other': 'shrödinger'
  184. }
  185. const clientPackages = {
  186. '@foo/bar': 'read-write',
  187. '@foo/util': 'read-only',
  188. '@foo/other': 'shrödinger'
  189. }
  190. tnock(t, REG).get(
  191. '/-/org/myorg/package?format=cli'
  192. ).reply(200, serverPackages)
  193. return access.lsPackages('myorg', OPTS).then(data => {
  194. t.deepEqual(data, clientPackages, 'got client package info')
  195. })
  196. })
  197. t.test('ls-packages on user', t => {
  198. const serverPackages = {
  199. '@foo/bar': 'write',
  200. '@foo/util': 'read',
  201. '@foo/other': 'shrödinger'
  202. }
  203. const clientPackages = {
  204. '@foo/bar': 'read-write',
  205. '@foo/util': 'read-only',
  206. '@foo/other': 'shrödinger'
  207. }
  208. const srv = tnock(t, REG)
  209. srv.get('/-/org/myuser/package?format=cli').reply(404, { error: 'not found' })
  210. srv.get('/-/user/myuser/package?format=cli').reply(200, serverPackages)
  211. return access.lsPackages('myuser', OPTS).then(data => {
  212. t.deepEqual(data, clientPackages, 'got client package info')
  213. })
  214. })
  215. t.test('ls-packages error on team', t => {
  216. tnock(t, REG).get('/-/team/myorg/myteam/package?format=cli').reply(404)
  217. return access.lsPackages('myorg:myteam', OPTS).then(
  218. () => { throw new Error('should not have succeeded') },
  219. err => t.equal(err.code, 'E404', 'spit out 404 directly if team provided')
  220. )
  221. })
  222. t.test('ls-packages error on user', t => {
  223. const srv = tnock(t, REG)
  224. srv.get('/-/org/myuser/package?format=cli').reply(404, { error: 'not found' })
  225. srv.get('/-/user/myuser/package?format=cli').reply(404, { error: 'not found' })
  226. return access.lsPackages('myuser', OPTS).then(
  227. () => { throw new Error('should not have succeeded') },
  228. err => t.equal(err.code, 'E404', 'spit out 404 if both reqs fail')
  229. )
  230. })
  231. t.test('ls-packages bad response', t => {
  232. tnock(t, REG).get(
  233. '/-/team/myorg/myteam/package?format=cli'
  234. ).reply(200, JSON.stringify(null))
  235. return access.lsPackages('myorg:myteam', OPTS).then(data => {
  236. t.deepEqual(data, null, 'succeeds with null')
  237. })
  238. })
  239. t.test('ls-packages stream', t => {
  240. const serverPackages = {
  241. '@foo/bar': 'write',
  242. '@foo/util': 'read',
  243. '@foo/other': 'shrödinger'
  244. }
  245. const clientPackages = [
  246. ['@foo/bar', 'read-write'],
  247. ['@foo/util', 'read-only'],
  248. ['@foo/other', 'shrödinger']
  249. ]
  250. tnock(t, REG).get(
  251. '/-/team/myorg/myteam/package?format=cli'
  252. ).reply(200, serverPackages)
  253. return access.lsPackages.stream('myorg:myteam', OPTS)
  254. .collect()
  255. .then(data => {
  256. t.deepEqual(data, clientPackages, 'got streamed client package info')
  257. })
  258. })
  259. t.test('ls-packages stream no opts', t => {
  260. const serverPackages = {
  261. '@foo/bar': 'write',
  262. '@foo/util': 'read',
  263. '@foo/other': 'shrödinger'
  264. }
  265. const clientPackages = [
  266. ['@foo/bar', 'read-write'],
  267. ['@foo/util', 'read-only'],
  268. ['@foo/other', 'shrödinger']
  269. ]
  270. // NOTE: mocking real url, because no opts variable means `registry` value
  271. // will be defauled to real registry url
  272. tnock(t, 'https://registry.npmjs.org')
  273. .get('/-/team/myorg/myteam/package?format=cli')
  274. .reply(200, serverPackages)
  275. return access.lsPackages.stream('myorg:myteam')
  276. .collect()
  277. .then(data => {
  278. t.deepEqual(data, clientPackages, 'got streamed client package info')
  279. })
  280. })
  281. t.test('ls-collaborators', t => {
  282. const serverCollaborators = {
  283. 'myorg:myteam': 'write',
  284. 'myorg:anotherteam': 'read',
  285. 'myorg:thirdteam': 'special-case'
  286. }
  287. const clientCollaborators = {
  288. 'myorg:myteam': 'read-write',
  289. 'myorg:anotherteam': 'read-only',
  290. 'myorg:thirdteam': 'special-case'
  291. }
  292. tnock(t, REG).get(
  293. '/-/package/%40foo%2Fbar/collaborators?format=cli'
  294. ).reply(200, serverCollaborators)
  295. return access.lsCollaborators('@foo/bar', OPTS).then(data => {
  296. t.deepEqual(data, clientCollaborators, 'got collaborators')
  297. })
  298. })
  299. t.test('ls-collaborators stream', t => {
  300. const serverCollaborators = {
  301. 'myorg:myteam': 'write',
  302. 'myorg:anotherteam': 'read',
  303. 'myorg:thirdteam': 'special-case'
  304. }
  305. const clientCollaborators = [
  306. ['myorg:myteam', 'read-write'],
  307. ['myorg:anotherteam', 'read-only'],
  308. ['myorg:thirdteam', 'special-case']
  309. ]
  310. tnock(t, REG).get(
  311. '/-/package/%40foo%2Fbar/collaborators?format=cli'
  312. ).reply(200, serverCollaborators)
  313. return access.lsCollaborators.stream('@foo/bar', OPTS)
  314. .collect()
  315. .then(data => {
  316. t.deepEqual(data, clientCollaborators, 'got collaborators')
  317. })
  318. })
  319. t.test('ls-collaborators w/scope', t => {
  320. const serverCollaborators = {
  321. 'myorg:myteam': 'write',
  322. 'myorg:anotherteam': 'read',
  323. 'myorg:thirdteam': 'special-case'
  324. }
  325. const clientCollaborators = {
  326. 'myorg:myteam': 'read-write',
  327. 'myorg:anotherteam': 'read-only',
  328. 'myorg:thirdteam': 'special-case'
  329. }
  330. tnock(t, REG).get(
  331. '/-/package/%40foo%2Fbar/collaborators?format=cli&user=zkat'
  332. ).reply(200, serverCollaborators)
  333. return access.lsCollaborators('@foo/bar', 'zkat', OPTS).then(data => {
  334. t.deepEqual(data, clientCollaborators, 'got collaborators')
  335. })
  336. })
  337. t.test('ls-collaborators w/o scope', t => {
  338. const serverCollaborators = {
  339. 'myorg:myteam': 'write',
  340. 'myorg:anotherteam': 'read',
  341. 'myorg:thirdteam': 'special-case'
  342. }
  343. const clientCollaborators = {
  344. 'myorg:myteam': 'read-write',
  345. 'myorg:anotherteam': 'read-only',
  346. 'myorg:thirdteam': 'special-case'
  347. }
  348. tnock(t, REG).get(
  349. '/-/package/bar/collaborators?format=cli&user=zkat'
  350. ).reply(200, serverCollaborators)
  351. return access.lsCollaborators('bar', 'zkat', OPTS).then(data => {
  352. t.deepEqual(data, clientCollaborators, 'got collaborators')
  353. })
  354. })
  355. t.test('ls-collaborators bad response', t => {
  356. tnock(t, REG).get(
  357. '/-/package/%40foo%2Fbar/collaborators?format=cli'
  358. ).reply(200, JSON.stringify(null))
  359. return access.lsCollaborators('@foo/bar', null, OPTS).then(data => {
  360. t.deepEqual(data, null, 'succeeds with null')
  361. })
  362. })
  363. t.test('error on non-registry specs', t => {
  364. const resolve = () => { throw new Error('should not succeed') }
  365. const reject = err => t.match(
  366. err.message, /spec.*must be a registry spec/, 'registry spec required'
  367. )
  368. return Promise.all([
  369. access.public('githubusername/reponame').then(resolve, reject),
  370. access.restricted('foo/bar').then(resolve, reject),
  371. access.grant('foo/bar', 'myorg', 'myteam', 'read-only').then(resolve, reject),
  372. access.revoke('foo/bar', 'myorg', 'myteam').then(resolve, reject),
  373. access.lsCollaborators('foo/bar').then(resolve, reject),
  374. access.tfaRequired('foo/bar').then(resolve, reject),
  375. access.tfaNotRequired('foo/bar').then(resolve, reject)
  376. ])
  377. })
  378. t.test('edit', t => {
  379. t.equal(typeof access.edit, 'function', 'access.edit exists')
  380. t.throws(() => {
  381. access.edit()
  382. }, /Not implemented/, 'directly throws NIY message')
  383. t.done()
  384. })