package-manager-spec.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. const path = require('path');
  2. const process = require('process');
  3. const PackageManager = require('../lib/package-manager');
  4. describe("PackageManager", function() {
  5. let [packageManager] = [];
  6. beforeEach(function() {
  7. spyOn(atom.packages, 'getApmPath').andReturn('/an/invalid/apm/command/to/run');
  8. atom.config.set('core.useProxySettingsWhenCallingApm', false);
  9. packageManager = new PackageManager();
  10. });
  11. it("handle errors spawning apm", function() {
  12. const noSuchCommandError = process.platform === 'win32' ? ' cannot find the path ' : 'ENOENT';
  13. waitsForPromise({shouldReject: true}, () => packageManager.getInstalled());
  14. waitsForPromise({shouldReject: true}, () => packageManager.getOutdated());
  15. waitsForPromise({shouldReject: true}, () => packageManager.getFeatured());
  16. waitsForPromise({shouldReject: true}, () => packageManager.getPackage('foo'));
  17. const installCallback = jasmine.createSpy('installCallback');
  18. const uninstallCallback = jasmine.createSpy('uninstallCallback');
  19. const updateCallback = jasmine.createSpy('updateCallback');
  20. runs(() => packageManager.install({name: 'foo', version: '1.0.0'}, installCallback));
  21. waitsFor(() => installCallback.callCount === 1);
  22. runs(function() {
  23. const installArg = installCallback.argsForCall[0][0];
  24. expect(installArg.message).toBe("Installing \u201Cfoo@1.0.0\u201D failed.");
  25. expect(installArg.packageInstallError).toBe(true);
  26. expect(installArg.stderr).toContain(noSuchCommandError);
  27. return packageManager.uninstall({name: 'foo'}, uninstallCallback);
  28. });
  29. waitsFor(() => uninstallCallback.callCount === 1);
  30. runs(function() {
  31. const uninstallArg = uninstallCallback.argsForCall[0][0];
  32. expect(uninstallArg.message).toBe("Uninstalling \u201Cfoo\u201D failed.");
  33. expect(uninstallArg.stderr).toContain(noSuchCommandError);
  34. return packageManager.update({name: 'foo'}, '1.0.0', updateCallback);
  35. });
  36. waitsFor(() => updateCallback.callCount === 1);
  37. return runs(function() {
  38. const updateArg = updateCallback.argsForCall[0][0];
  39. expect(updateArg.message).toBe("Updating to \u201Cfoo@1.0.0\u201D failed.");
  40. expect(updateArg.packageInstallError).toBe(true);
  41. expect(updateArg.stderr).toContain(noSuchCommandError);
  42. });
  43. });
  44. describe("::isPackageInstalled()", function() {
  45. it("returns false a package is not installed", () => expect(packageManager.isPackageInstalled('some-package')).toBe(false));
  46. it("returns true when a package is loaded", function() {
  47. spyOn(atom.packages, 'isPackageLoaded').andReturn(true);
  48. expect(packageManager.isPackageInstalled('some-package')).toBe(true);
  49. });
  50. it("returns true when a package is disabled", function() {
  51. spyOn(atom.packages, 'getAvailablePackageNames').andReturn(['some-package']);
  52. expect(packageManager.isPackageInstalled('some-package')).toBe(true);
  53. });
  54. });
  55. describe("::install()", function() {
  56. let [runArgs, runCallback] = [];
  57. beforeEach(() => spyOn(packageManager, 'runCommand').andCallFake(function(args, callback) {
  58. runArgs = args;
  59. runCallback = callback;
  60. return {onWillThrowError() {}};
  61. }));
  62. it("installs the latest version when a package version is not specified", function() {
  63. packageManager.install({name: 'something'}, function() {});
  64. expect(packageManager.runCommand).toHaveBeenCalled();
  65. expect(runArgs).toEqual(['install', 'something', '--json']);
  66. });
  67. it("installs the package@version when a version is specified", function() {
  68. packageManager.install({name: 'something', version: '0.2.3'}, function() {});
  69. expect(packageManager.runCommand).toHaveBeenCalled();
  70. expect(runArgs).toEqual(['install', 'something@0.2.3', '--json']);
  71. });
  72. describe("git url installation", function() {
  73. it('installs https:// urls', function() {
  74. const url = "https://github.com/user/repo.git";
  75. packageManager.install({name: url});
  76. expect(packageManager.runCommand).toHaveBeenCalled();
  77. expect(runArgs).toEqual(['install', 'https://github.com/user/repo.git', '--json']);
  78. });
  79. it('installs git@ urls', function() {
  80. const url = "git@github.com:user/repo.git";
  81. packageManager.install({name: url});
  82. expect(packageManager.runCommand).toHaveBeenCalled();
  83. expect(runArgs).toEqual(['install', 'git@github.com:user/repo.git', '--json']);
  84. });
  85. it('installs user/repo url shortcuts', function() {
  86. const url = "user/repo";
  87. packageManager.install({name: url});
  88. expect(packageManager.runCommand).toHaveBeenCalled();
  89. expect(runArgs).toEqual(['install', 'user/repo', '--json']);
  90. });
  91. it('installs and activates git pacakges with names different from the repo name', function() {
  92. spyOn(atom.packages, 'activatePackage');
  93. packageManager.install({name: 'git-repo-name'});
  94. const json = {
  95. metadata: {
  96. name: 'real-package-name'
  97. }
  98. };
  99. runCallback(0, JSON.stringify([json]), '');
  100. expect(atom.packages.activatePackage).toHaveBeenCalledWith(json.metadata.name);
  101. });
  102. it('emits an installed event with a copy of the pack including the full package metadata', function() {
  103. spyOn(packageManager, 'emitPackageEvent');
  104. const originalPackObject = {name: 'git-repo-name', otherData: {will: 'beCopied'}};
  105. packageManager.install(originalPackObject);
  106. const json = {
  107. metadata: {
  108. name: 'real-package-name',
  109. moreInfo: 'yep'
  110. }
  111. };
  112. runCallback(0, JSON.stringify([json]), '');
  113. let installEmittedCount = 0;
  114. for (let call of Array.from(packageManager.emitPackageEvent.calls)) {
  115. if (call.args[0] === "installed") {
  116. expect(call.args[1]).not.toEqual(originalPackObject);
  117. expect(call.args[1].moreInfo).toEqual("yep");
  118. expect(call.args[1].otherData).toBe(originalPackObject.otherData);
  119. installEmittedCount++;
  120. }
  121. }
  122. expect(installEmittedCount).toBe(1);
  123. });
  124. });
  125. });
  126. describe("::uninstall()", function() {
  127. let [runCallback] = [];
  128. beforeEach(function() {
  129. spyOn(packageManager, 'unload');
  130. return spyOn(packageManager, 'runCommand').andCallFake(function(args, callback) {
  131. runCallback = callback;
  132. return {onWillThrowError() {}};
  133. });
  134. });
  135. it("removes the package from the core.disabledPackages list", function() {
  136. atom.config.set('core.disabledPackages', ['something']);
  137. packageManager.uninstall({name: 'something'}, function() {});
  138. expect(atom.config.get('core.disabledPackages')).toContain('something');
  139. runCallback(0, '', '');
  140. expect(atom.config.get('core.disabledPackages')).not.toContain('something');
  141. });
  142. });
  143. describe("::packageHasSettings", function() {
  144. it("returns true when the pacakge has config", function() {
  145. atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-config'));
  146. expect(packageManager.packageHasSettings('package-with-config')).toBe(true);
  147. });
  148. it("returns false when the pacakge does not have config and doesn't define language grammars", () => expect(packageManager.packageHasSettings('random-package')).toBe(false));
  149. it("returns true when the pacakge does not have config, but does define language grammars", function() {
  150. const packageName = 'language-test';
  151. waitsForPromise(() => atom.packages.activatePackage(path.join(__dirname, 'fixtures', packageName)));
  152. return runs(() => expect(packageManager.packageHasSettings(packageName)).toBe(true));
  153. });
  154. });
  155. describe("::loadOutdated", function() {
  156. it("caches results", function() {
  157. spyOn(packageManager, 'runCommand').andCallFake(function(args, callback) {
  158. callback(0, '[{"name": "boop"}]', '');
  159. return {onWillThrowError() {}};
  160. });
  161. packageManager.loadOutdated(false, function() {});
  162. expect(packageManager.apmCache.loadOutdated.value).toMatch([{"name": "boop"}]);
  163. packageManager.loadOutdated(false, function() {});
  164. expect(packageManager.runCommand.calls.length).toBe(1);
  165. });
  166. it("expires results after a timeout", function() {
  167. spyOn(packageManager, 'runCommand').andCallFake(function(args, callback) {
  168. callback(0, '[{"name": "boop"}]', '');
  169. return {onWillThrowError() {}};
  170. });
  171. packageManager.loadOutdated(false, function() {});
  172. const now = Date.now();
  173. if (!Date.now.andReturn) { spyOn(Date, 'now'); }
  174. Date.now.andReturn(((() => now + packageManager.CACHE_EXPIRY + 1))());
  175. packageManager.loadOutdated(false, function() {});
  176. expect(packageManager.runCommand.calls.length).toBe(2);
  177. });
  178. it("expires results after a package updated/installed", function() {
  179. packageManager.apmCache.loadOutdated = {
  180. value: ['hi'],
  181. expiry: Date.now() + 999999999
  182. };
  183. spyOn(packageManager, 'runCommand').andCallFake(function(args, callback) {
  184. callback(0, '[{"name": "boop"}]', '');
  185. return {onWillThrowError() {}};
  186. });
  187. // Just prevent this stuff from calling through, it doesn't matter for this test
  188. spyOn(atom.packages, 'deactivatePackage').andReturn(true);
  189. spyOn(atom.packages, 'activatePackage').andReturn(true);
  190. spyOn(atom.packages, 'unloadPackage').andReturn(true);
  191. spyOn(atom.packages, 'loadPackage').andReturn(true);
  192. packageManager.loadOutdated(false, function() {});
  193. expect(packageManager.runCommand.calls.length).toBe(0);
  194. packageManager.update({}, {}, function() {}); // +1 runCommand call to update the package
  195. packageManager.loadOutdated(false, function() {}); // +1 runCommand call to load outdated because the cache should be wiped
  196. expect(packageManager.runCommand.calls.length).toBe(2);
  197. packageManager.install({}, function() {}); // +1 runCommand call to install the package
  198. packageManager.loadOutdated(false, function() {}); // +1 runCommand call to load outdated because the cache should be wiped
  199. expect(packageManager.runCommand.calls.length).toBe(4);
  200. packageManager.loadOutdated(false, function() {}); // +0 runCommand call, should be cached
  201. expect(packageManager.runCommand.calls.length).toBe(4);
  202. });
  203. it("expires results if it is called with clearCache set to true", function() {
  204. packageManager.apmCache.loadOutdated = {
  205. value: ['hi'],
  206. expiry: Date.now() + 999999999
  207. };
  208. spyOn(packageManager, 'runCommand').andCallFake(function(args, callback) {
  209. callback(0, '[{"name": "boop"}]', '');
  210. return {onWillThrowError() {}};
  211. });
  212. packageManager.loadOutdated(true, function() {});
  213. expect(packageManager.runCommand.calls.length).toBe(1);
  214. expect(packageManager.apmCache.loadOutdated.value).toEqual([{"name": "boop"}]);
  215. });
  216. describe("when there is a version pinned package", function() {
  217. beforeEach(() => atom.config.set('core.versionPinnedPackages', ['beep']));
  218. it("caches results", function() {
  219. spyOn(packageManager, 'runCommand').andCallFake(function(args, callback) {
  220. callback(0, '[{"name": "boop"}, {"name": "beep"}]', '');
  221. return {onWillThrowError() {}};
  222. });
  223. packageManager.loadOutdated(false, function() {});
  224. expect(packageManager.apmCache.loadOutdated.value).toMatch([{"name": "boop"}]);
  225. packageManager.loadOutdated(false, function() {});
  226. expect(packageManager.runCommand.calls.length).toBe(1);
  227. });
  228. it("expires results after a timeout", function() {
  229. spyOn(packageManager, 'runCommand').andCallFake(function(args, callback) {
  230. callback(0, '[{"name": "boop"}, {"name": "beep"}]', '');
  231. return {onWillThrowError() {}};
  232. });
  233. packageManager.loadOutdated(false, function() {});
  234. const now = Date.now();
  235. if (!Date.now.andReturn) { spyOn(Date, 'now'); }
  236. Date.now.andReturn(((() => now + packageManager.CACHE_EXPIRY + 1))());
  237. packageManager.loadOutdated(false, function() {});
  238. expect(packageManager.runCommand.calls.length).toBe(2);
  239. });
  240. it("expires results after a package updated/installed", function() {
  241. packageManager.apmCache.loadOutdated = {
  242. value: ['hi'],
  243. expiry: Date.now() + 999999999
  244. };
  245. spyOn(packageManager, 'runCommand').andCallFake(function(args, callback) {
  246. callback(0, '[{"name": "boop"}, {"name": "beep"}]', '');
  247. return {onWillThrowError() {}};
  248. });
  249. // Just prevent this stuff from calling through, it doesn't matter for this test
  250. spyOn(atom.packages, 'deactivatePackage').andReturn(true);
  251. spyOn(atom.packages, 'activatePackage').andReturn(true);
  252. spyOn(atom.packages, 'unloadPackage').andReturn(true);
  253. spyOn(atom.packages, 'loadPackage').andReturn(true);
  254. packageManager.loadOutdated(false, function() {});
  255. expect(packageManager.runCommand.calls.length).toBe(0);
  256. packageManager.update({}, {}, function() {}); // +1 runCommand call to update the package
  257. packageManager.loadOutdated(false, function() {}); // +1 runCommand call to load outdated because the cache should be wiped
  258. expect(packageManager.runCommand.calls.length).toBe(2);
  259. packageManager.install({}, function() {}); // +1 runCommand call to install the package
  260. packageManager.loadOutdated(false, function() {}); // +1 runCommand call to load outdated because the cache should be wiped
  261. expect(packageManager.runCommand.calls.length).toBe(4);
  262. packageManager.loadOutdated(false, function() {}); // +0 runCommand call, should be cached
  263. expect(packageManager.runCommand.calls.length).toBe(4);
  264. });
  265. it("expires results if it is called with clearCache set to true", function() {
  266. packageManager.apmCache.loadOutdated = {
  267. value: ['hi'],
  268. expiry: Date.now() + 999999999
  269. };
  270. spyOn(packageManager, 'runCommand').andCallFake(function(args, callback) {
  271. callback(0, '[{"name": "boop"}, {"name": "beep"}]', '');
  272. return {onWillThrowError() {}};
  273. });
  274. packageManager.loadOutdated(true, function() {});
  275. expect(packageManager.runCommand.calls.length).toBe(1);
  276. expect(packageManager.apmCache.loadOutdated.value).toEqual([{"name": "boop"}]);
  277. });
  278. });
  279. });
  280. });