test_lockfile.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. import pytest
  2. import io
  3. from build.plugins.lib.nots.package_manager.pnpm.lockfile import PnpmLockfile, PnpmLockfileHelper
  4. @pytest.fixture()
  5. def patch_open_v6(monkeypatch):
  6. def mock_open(a, b):
  7. file_like = io.BytesIO(b'lockfileVersion: "6.0"')
  8. return io.BufferedReader(file_like)
  9. monkeypatch.setattr(io, "open", mock_open)
  10. @pytest.fixture()
  11. def patch_open_v9(monkeypatch):
  12. def mock_open(a, b):
  13. file_like = io.BytesIO(b'lockfileVersion: "9.0"')
  14. return io.BufferedReader(file_like)
  15. monkeypatch.setattr(io, "open", mock_open)
  16. @pytest.fixture()
  17. def patch_open_incorrect_version(monkeypatch):
  18. def mock_open(a, b):
  19. file_like = io.BytesIO(b'lockfileVersion: 0')
  20. return io.BufferedReader(file_like)
  21. monkeypatch.setattr(io, "open", mock_open)
  22. @pytest.fixture()
  23. def patch_open_no_version(monkeypatch):
  24. def mock_open(a, b):
  25. file_like = io.BytesIO(b'some text')
  26. return io.BufferedReader(file_like)
  27. monkeypatch.setattr(io, "open", mock_open)
  28. def test_lockfile_read_v6(patch_open_v6):
  29. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  30. lf.read()
  31. assert lf.data == {"lockfileVersion": "9.0"}
  32. def test_lockfile_read_v9(patch_open_v9):
  33. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  34. lf.read()
  35. assert lf.data == {"lockfileVersion": "9.0"}
  36. def test_lockfile_read_yaml_error_incorrect_lockfile_version(patch_open_incorrect_version):
  37. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  38. with pytest.raises(Exception) as e:
  39. lf.read()
  40. assert str(e.value) == (
  41. 'Error of project configuration: /pnpm-lock.yaml has lockfileVersion: 0.\n'
  42. + 'This version is not supported. Please, delete pnpm-lock.yaml and regenerate it using `ya tool nots --clean update-lockfile`'
  43. )
  44. def test_lockfile_read_yaml_error_no_lockfile_version(patch_open_no_version):
  45. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  46. with pytest.raises(Exception) as e:
  47. lf.read()
  48. assert str(e.value) == (
  49. 'Error of project configuration: /pnpm-lock.yaml has lockfileVersion: <no-version>.\n'
  50. + 'This version is not supported. Please, delete pnpm-lock.yaml and regenerate it using `ya tool nots --clean update-lockfile`'
  51. )
  52. def test_lockfile_get_packages_meta_ok():
  53. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  54. lf.data = {
  55. "packages": {
  56. "/@babel/cli/7.6.2_@babel+core@7.6.2": {
  57. "resolution": {
  58. "integrity": "sha512-JDZ+T/br9pPfT2lmAMJypJDTTTHM9ePD/ED10TRjRzJVdEVy+JB3iRlhzYmTt5YkNgHvxWGlUVnLtdv6ruiDrQ==",
  59. "tarball": "@babel%2fcli/-/cli-7.6.2.tgz?rbtorrent=cb1849da3e4947e56a8f6bde6a1ec42703ddd187",
  60. },
  61. },
  62. },
  63. }
  64. packages = list(lf.get_packages_meta())
  65. pkg = packages[0]
  66. assert len(packages) == 1
  67. assert pkg.tarball_url == "@babel%2fcli/-/cli-7.6.2.tgz"
  68. assert pkg.sky_id == "rbtorrent:cb1849da3e4947e56a8f6bde6a1ec42703ddd187"
  69. assert pkg.integrity == "JDZ+T/br9pPfT2lmAMJypJDTTTHM9ePD/ED10TRjRzJVdEVy+JB3iRlhzYmTt5YkNgHvxWGlUVnLtdv6ruiDrQ=="
  70. assert pkg.integrity_algorithm == "sha512"
  71. def test_lockfile_get_packages_empty():
  72. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  73. lf.data = {}
  74. assert len(list(lf.get_packages_meta())) == 0
  75. def test_package_meta_invalid_key():
  76. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  77. lf.data = {
  78. "packages": {
  79. "in/valid": {},
  80. },
  81. }
  82. with pytest.raises(TypeError) as e:
  83. list(lf.get_packages_meta())
  84. assert str(e.value) == "Invalid package meta for 'in/valid', missing 'resolution' key"
  85. def test_package_meta_missing_resolution():
  86. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  87. lf.data = {
  88. "packages": {
  89. "/valid@1.2.3": {},
  90. },
  91. }
  92. with pytest.raises(TypeError) as e:
  93. list(lf.get_packages_meta())
  94. assert str(e.value) == "Invalid package meta for '/valid@1.2.3', missing 'resolution' key"
  95. def test_package_meta_missing_tarball():
  96. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  97. lf.data = {
  98. "packages": {
  99. "/valid@1.2.3": {
  100. "resolution": {},
  101. },
  102. },
  103. }
  104. with pytest.raises(TypeError) as e:
  105. list(lf.get_packages_meta())
  106. assert str(e.value) == "Invalid package meta for '/valid@1.2.3', missing 'tarball' key"
  107. def test_lockfile_convertion_to_v9_simple():
  108. data = {
  109. "lockfileVersion": "6.0",
  110. "packages": {
  111. "/abc@1.2.3": {
  112. "resolution": {
  113. "integrity": "some-integrity",
  114. "tarball": "https://npm.yandex-team.ru/some/module/path",
  115. },
  116. "dependencies": {
  117. "dep1": "2.3.4",
  118. "dep2": "3.4.5",
  119. },
  120. "dev": False,
  121. }
  122. },
  123. }
  124. converted_data = PnpmLockfileHelper.ensure_v9(data)
  125. assert converted_data == {
  126. "lockfileVersion": "9.0",
  127. "packages": {
  128. "abc@1.2.3": {
  129. "resolution": {"integrity": "some-integrity", "tarball": "https://npm.yandex-team.ru/some/module/path"}
  130. }
  131. },
  132. "snapshots": {"abc@1.2.3": {"dependencies": {"dep1": "2.3.4", "dep2": "3.4.5"}}},
  133. }
  134. def test_lockfile_convertion_to_v9_dependencies():
  135. data = {
  136. "lockfileVersion": "6.0",
  137. "dependencies": {
  138. "@yandex-int/static-uploader": {
  139. "specifier": "^1.0.1",
  140. "version": "1.0.3",
  141. }
  142. },
  143. "packages": {
  144. "/abc@1.2.3": {
  145. "resolution": {
  146. "integrity": "some-integrity",
  147. "tarball": "https://npm.yandex-team.ru/some/module/path",
  148. },
  149. "dependencies": {
  150. "dep1": "2.3.4",
  151. "dep2": "3.4.5",
  152. },
  153. "dev": False,
  154. }
  155. },
  156. }
  157. converted_data = PnpmLockfileHelper.ensure_v9(data)
  158. assert converted_data == {
  159. "lockfileVersion": "9.0",
  160. "packages": {
  161. "abc@1.2.3": {
  162. "resolution": {"integrity": "some-integrity", "tarball": "https://npm.yandex-team.ru/some/module/path"}
  163. }
  164. },
  165. "importers": {
  166. ".": {"dependencies": {"@yandex-int/static-uploader": {"specifier": "^1.0.1", "version": "1.0.3"}}}
  167. },
  168. "snapshots": {"abc@1.2.3": {"dependencies": {"dep1": "2.3.4", "dep2": "3.4.5"}}},
  169. }
  170. def test_lockfile_convertion_to_v9():
  171. data = {
  172. "lockfileVersion": "6.0",
  173. "packages": {
  174. "/ajv@6.12.6": {
  175. "resolution": {
  176. "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
  177. "tarball": "https://npm.yandex-team.ru/ajv/-/ajv-6.12.6.tgz?rbtorrent=8700a2f2e42ac5b59b5c6d8142cd2bfd19a56001",
  178. },
  179. "dependencies": {
  180. "fast-deep-equal": "3.1.3",
  181. "fast-json-stable-stringify": "2.1.0",
  182. },
  183. "engines": {"node": '>=10'},
  184. "dev": False,
  185. },
  186. "/@typescript-eslint/visitor-keys@8.7.0": {
  187. "resolution": {
  188. "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
  189. "tarball": "https://npm.yandex-team.ru/@typescript-eslint%2fvisitor-keys/-/visitor-keys-8.7.0.tgz?rbtorrent=",
  190. },
  191. "dependencies": {
  192. "@typescript-eslint/types": "8.7.0",
  193. "eslint-visitor-keys": "3.4.3",
  194. },
  195. "engines": {"node": ">=12"},
  196. "dev": False,
  197. },
  198. "/@csstools/media-query-list-parser@3.0.1(@csstools/css-parser-algorithms@3.0.1)(@csstools/css-tokenizer@3.0.1)": {
  199. "resolution": {
  200. "integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==",
  201. "tarball": "https://npm.yandex-team.ru/@csstools%2fmedia-query-list-parser/-/media-query-list-parser-3.0.1.tgz?rbtorrent=",
  202. },
  203. "engines": {"node": ">=18"},
  204. "peerDependencies": {
  205. "@csstools/css-parser-algorithms": "^3.0.1",
  206. "@csstools/css-tokenizer": "^3.0.1",
  207. },
  208. "dependencies": {
  209. "@csstools/css-parser-algorithms": "3.0.1(@csstools/css-tokenizer@3.0.1)",
  210. "@csstools/css-tokenizer": "3.0.1",
  211. },
  212. "dev": False,
  213. },
  214. },
  215. }
  216. converted_data = PnpmLockfileHelper.ensure_v9(data)
  217. assert converted_data == {
  218. "lockfileVersion": "9.0",
  219. "packages": {
  220. "ajv@6.12.6": {
  221. "resolution": {
  222. "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
  223. "tarball": "https://npm.yandex-team.ru/ajv/-/ajv-6.12.6.tgz?rbtorrent=8700a2f2e42ac5b59b5c6d8142cd2bfd19a56001",
  224. },
  225. "engines": {"node": ">=10"},
  226. },
  227. "@typescript-eslint/visitor-keys@8.7.0": {
  228. "resolution": {
  229. "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
  230. "tarball": "https://npm.yandex-team.ru/@typescript-eslint%2fvisitor-keys/-/visitor-keys-8.7.0.tgz?rbtorrent=",
  231. },
  232. "engines": {"node": ">=12"},
  233. },
  234. "@csstools/media-query-list-parser@3.0.1": {
  235. "resolution": {
  236. "integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==",
  237. "tarball": "https://npm.yandex-team.ru/@csstools%2fmedia-query-list-parser/-/media-query-list-parser-3.0.1.tgz?rbtorrent=",
  238. },
  239. "engines": {"node": ">=18"},
  240. "peerDependencies": {"@csstools/css-parser-algorithms": "^3.0.1", "@csstools/css-tokenizer": "^3.0.1"},
  241. },
  242. },
  243. "snapshots": {
  244. "ajv@6.12.6": {"dependencies": {"fast-deep-equal": "3.1.3", "fast-json-stable-stringify": "2.1.0"}},
  245. "@typescript-eslint/visitor-keys@8.7.0": {
  246. "dependencies": {"@typescript-eslint/types": "8.7.0", "eslint-visitor-keys": "3.4.3"}
  247. },
  248. "@csstools/media-query-list-parser@3.0.1(@csstools/css-parser-algorithms@3.0.1)(@csstools/css-tokenizer@3.0.1)": {
  249. "dependencies": {
  250. "@csstools/css-parser-algorithms": "3.0.1(@csstools/css-tokenizer@3.0.1)",
  251. "@csstools/css-tokenizer": "3.0.1",
  252. }
  253. },
  254. },
  255. }
  256. def test_package_meta_missing_rbtorrent():
  257. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  258. lf.data = {
  259. "packages": {
  260. "/valid@1.2.3": {
  261. "resolution": {
  262. "integrity": "sha512-JDZ+T/br9pPfT2lmAMJypJDTTTHM9ePD/ED10TRjRzJVdEVy+JB3iRlhzYmTt5YkNgHvxWGlUVnLtdv6ruiDrQ==",
  263. "tarball": "valid-without-rbtorrent-1.2.3.tgz",
  264. },
  265. },
  266. },
  267. }
  268. packages = list(lf.get_packages_meta())
  269. pkg = packages[0]
  270. assert len(packages) == 1
  271. assert pkg.sky_id == ""
  272. def test_lockfile_meta_file_tarball_prohibits_file_protocol():
  273. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  274. lf.data = {
  275. "packages": {
  276. "/@babel/cli@7.6.2": {
  277. "resolution": {
  278. "integrity": "sha512-JDZ+T/br9pPfT2lmAMJypJDTTTHM9ePD/ED10TRjRzJVdEVy+JB3iRlhzYmTt5YkNgHvxWGlUVnLtdv6ruiDrQ==",
  279. "tarball": "file:/some/abs/path.tgz",
  280. },
  281. },
  282. },
  283. }
  284. with pytest.raises(TypeError) as e:
  285. list(lf.get_packages_meta())
  286. assert (
  287. str(e.value)
  288. == "Invalid package meta for '/@babel/cli@7.6.2', parse error: tarball cannot point to a file, got 'file:/some/abs/path.tgz'"
  289. )
  290. def test_lockfile_update_tarball_resolutions_ok():
  291. lf = PnpmLockfile(path="/pnpm-lock.yaml")
  292. lf.data = {
  293. "packages": {
  294. "/@babel/cli@7.6.2_@babel+core@7.6.2": {
  295. "resolution": {
  296. "integrity": "sha512-JDZ+T/br9pPfT2lmAMJypJDTTTHM9ePD/ED10TRjRzJVdEVy+JB3iRlhzYmTt5YkNgHvxWGlUVnLtdv6ruiDrQ==",
  297. "tarball": "@babel%2fcli/-/cli-7.6.2.tgz?rbtorrent=cb1849da3e4947e56a8f6bde6a1ec42703ddd187",
  298. },
  299. },
  300. },
  301. }
  302. lf.update_tarball_resolutions(lambda p: p.tarball_url)
  303. assert (
  304. lf.data["packages"]["/@babel/cli@7.6.2_@babel+core@7.6.2"]["resolution"]["tarball"]
  305. == "@babel%2fcli/-/cli-7.6.2.tgz"
  306. )
  307. def test_lockfile_merge():
  308. lf1 = PnpmLockfile(path="/foo/pnpm-lock.yaml")
  309. lf1.data = {
  310. "lockfileVersion": "9.0",
  311. "dependencies": {
  312. "a": "1.0.0",
  313. },
  314. "packages": {
  315. "a@1.0.0": {},
  316. },
  317. "snapshots": {
  318. "a@1.0.0": {},
  319. },
  320. }
  321. lf2 = PnpmLockfile(path="/bar/pnpm-lock.yaml")
  322. lf2.data = {
  323. "lockfileVersion": "9.0",
  324. "dependencies": {
  325. "b": "1.0.0",
  326. },
  327. "packages": {
  328. "b@1.0.0": {},
  329. },
  330. "snapshots": {
  331. "b@1.0.0": {},
  332. },
  333. }
  334. lf3 = PnpmLockfile(path="/another/baz/pnpm-lock.yaml")
  335. lf3.data = {
  336. "lockfileVersion": "9.0",
  337. "importers": {
  338. ".": {
  339. "dependencies": {
  340. "@a/qux": "link:../qux",
  341. "a": "1.0.0",
  342. },
  343. "specifiers": {
  344. "@a/qux": "workspace:../qux",
  345. "a": "1.0.0",
  346. },
  347. },
  348. "../qux": {
  349. "dependencies": {
  350. "b": "1.0.1",
  351. },
  352. "specifiers": {
  353. "b": "1.0.1",
  354. },
  355. },
  356. },
  357. "packages": {
  358. "a@1.0.0": {},
  359. "b@1.0.1": {},
  360. },
  361. "snapshots": {
  362. "a@1.0.0": {},
  363. "b@1.0.1": {},
  364. },
  365. }
  366. lf4 = PnpmLockfile(path="/another/quux/pnpm-lock.yaml")
  367. lf4.data = {
  368. "lockfileVersion": "9.0",
  369. "importers": {
  370. ".": {
  371. "dependencies": {
  372. "@a/bar": "link:../../bar",
  373. }
  374. }
  375. },
  376. "specifiers": {
  377. "@a/bar": "workspace:../../bar",
  378. },
  379. }
  380. lf1.merge(lf2)
  381. lf1.merge(lf3)
  382. lf1.merge(lf4)
  383. assert lf1.data == {
  384. "lockfileVersion": "9.0",
  385. "packages": {"a@1.0.0": {}, "b@1.0.0": {}, "b@1.0.1": {}},
  386. "snapshots": {"a@1.0.0": {}, "b@1.0.0": {}, "b@1.0.1": {}},
  387. "importers": {
  388. ".": {"dependencies": {"a": "1.0.0"}},
  389. "../bar": {"dependencies": {"b": "1.0.0"}},
  390. "../another/baz": {
  391. "dependencies": {"@a/qux": "link:../qux", "a": "1.0.0"},
  392. "specifiers": {"@a/qux": "workspace:../qux", "a": "1.0.0"},
  393. },
  394. "../another/qux": {"dependencies": {"b": "1.0.1"}, "specifiers": {"b": "1.0.1"}},
  395. "../another/quux": {"dependencies": {"@a/bar": "link:../../bar"}},
  396. },
  397. }
  398. def test_lockfile_merge_dont_overrides_packages():
  399. lf1 = PnpmLockfile(path="/foo/pnpm-lock.yaml")
  400. lf1.data = {
  401. "lockfileVersion": "9.0",
  402. "dependencies": {
  403. "a": "1.0.0",
  404. },
  405. "importers": {
  406. ".": {
  407. "dependencies": {
  408. "a": "1.0.0",
  409. }
  410. }
  411. },
  412. "packages": {
  413. "a@1.0.0": {},
  414. },
  415. "snapshots": {
  416. "a@1.0.0": {},
  417. },
  418. }
  419. lf2 = PnpmLockfile(path="/bar/pnpm-lock.yaml")
  420. lf2.data = {
  421. "lockfileVersion": "9.0",
  422. "dependencies": {
  423. "a": "1.0.0",
  424. "b": "1.0.0",
  425. },
  426. "specifiers": {
  427. "a": "1.0.0",
  428. "b": "1.0.0",
  429. },
  430. "packages": {
  431. "a@1.0.0": {
  432. "overriden": True,
  433. },
  434. "b@1.0.0": {},
  435. },
  436. "snapshots": {
  437. "a@1.0.0": {
  438. "overriden": True,
  439. },
  440. "b@1.0.0": {},
  441. },
  442. }
  443. lf1.merge(lf2)
  444. assert lf1.data == {
  445. "lockfileVersion": "9.0",
  446. "importers": {
  447. ".": {
  448. "dependencies": {
  449. "a": "1.0.0",
  450. },
  451. },
  452. "../bar": {
  453. "dependencies": {
  454. "a": "1.0.0",
  455. "b": "1.0.0",
  456. },
  457. "specifiers": {
  458. "a": "1.0.0",
  459. "b": "1.0.0",
  460. },
  461. },
  462. },
  463. "packages": {
  464. "a@1.0.0": {},
  465. "b@1.0.0": {},
  466. },
  467. "snapshots": {
  468. "a@1.0.0": {},
  469. "b@1.0.0": {},
  470. },
  471. }