channel.e2e-spec.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. /* tslint:disable:no-non-null-assertion */
  2. import { DEFAULT_CHANNEL_CODE } from '@vendure/common/lib/shared-constants';
  3. import { createTestEnvironment, E2E_DEFAULT_CHANNEL_TOKEN } from '@vendure/testing';
  4. import gql from 'graphql-tag';
  5. import path from 'path';
  6. import { initialData } from '../../../e2e-common/e2e-initial-data';
  7. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  8. import {
  9. AssignProductsToChannel,
  10. CreateAdministrator,
  11. CreateChannel,
  12. CreateRole,
  13. CurrencyCode,
  14. DeleteChannel,
  15. DeletionResult,
  16. GetChannels,
  17. GetCustomerList,
  18. GetProductWithVariants,
  19. LanguageCode,
  20. Me,
  21. Permission,
  22. RemoveProductsFromChannel,
  23. UpdateChannel,
  24. UpdateGlobalSettings,
  25. } from './graphql/generated-e2e-admin-types';
  26. import {
  27. ASSIGN_PRODUCT_TO_CHANNEL,
  28. CREATE_ADMINISTRATOR,
  29. CREATE_CHANNEL,
  30. CREATE_ROLE,
  31. GET_CUSTOMER_LIST,
  32. GET_PRODUCT_WITH_VARIANTS,
  33. ME,
  34. REMOVE_PRODUCT_FROM_CHANNEL,
  35. } from './graphql/shared-definitions';
  36. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  37. describe('Channels', () => {
  38. const { server, adminClient, shopClient } = createTestEnvironment(testConfig);
  39. const SECOND_CHANNEL_TOKEN = 'second_channel_token';
  40. const THIRD_CHANNEL_TOKEN = 'third_channel_token';
  41. let secondChannelAdminRole: CreateRole.CreateRole;
  42. let customerUser: GetCustomerList.Items;
  43. beforeAll(async () => {
  44. await server.init({
  45. initialData,
  46. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  47. customerCount: 1,
  48. });
  49. await adminClient.asSuperAdmin();
  50. const { customers } = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
  51. GET_CUSTOMER_LIST,
  52. {
  53. options: { take: 1 },
  54. },
  55. );
  56. customerUser = customers.items[0];
  57. }, TEST_SETUP_TIMEOUT_MS);
  58. afterAll(async () => {
  59. await server.destroy();
  60. });
  61. it('create a new Channel', async () => {
  62. const { createChannel } = await adminClient.query<CreateChannel.Mutation, CreateChannel.Variables>(
  63. CREATE_CHANNEL,
  64. {
  65. input: {
  66. code: 'second-channel',
  67. token: SECOND_CHANNEL_TOKEN,
  68. defaultLanguageCode: LanguageCode.en,
  69. currencyCode: CurrencyCode.GBP,
  70. pricesIncludeTax: true,
  71. defaultShippingZoneId: 'T_1',
  72. defaultTaxZoneId: 'T_1',
  73. },
  74. },
  75. );
  76. expect(createChannel).toEqual({
  77. id: 'T_2',
  78. code: 'second-channel',
  79. token: SECOND_CHANNEL_TOKEN,
  80. currencyCode: 'GBP',
  81. defaultLanguageCode: 'en',
  82. defaultShippingZone: {
  83. id: 'T_1',
  84. },
  85. defaultTaxZone: {
  86. id: 'T_1',
  87. },
  88. pricesIncludeTax: true,
  89. });
  90. });
  91. it('superadmin has all permissions on new channel', async () => {
  92. const { me } = await adminClient.query<Me.Query>(ME);
  93. expect(me!.channels.length).toBe(2);
  94. const secondChannelData = me!.channels.find((c) => c.token === SECOND_CHANNEL_TOKEN);
  95. const nonOwnerPermissions = Object.values(Permission).filter((p) => p !== Permission.Owner);
  96. expect(secondChannelData!.permissions).toEqual(nonOwnerPermissions);
  97. });
  98. it('customer has Authenticated permission on new channel', async () => {
  99. await shopClient.asUserWithCredentials(customerUser.emailAddress, 'test');
  100. const { me } = await shopClient.query<Me.Query>(ME);
  101. expect(me!.channels.length).toBe(2);
  102. const secondChannelData = me!.channels.find((c) => c.token === SECOND_CHANNEL_TOKEN);
  103. expect(me!.channels).toEqual([
  104. {
  105. code: DEFAULT_CHANNEL_CODE,
  106. permissions: ['Authenticated'],
  107. token: E2E_DEFAULT_CHANNEL_TOKEN,
  108. },
  109. {
  110. code: 'second-channel',
  111. permissions: ['Authenticated'],
  112. token: SECOND_CHANNEL_TOKEN,
  113. },
  114. ]);
  115. });
  116. it('createRole on second Channel', async () => {
  117. const { createRole } = await adminClient.query<CreateRole.Mutation, CreateRole.Variables>(
  118. CREATE_ROLE,
  119. {
  120. input: {
  121. description: 'second channel admin',
  122. code: 'second-channel-admin',
  123. channelIds: ['T_2'],
  124. permissions: [
  125. Permission.ReadCatalog,
  126. Permission.ReadSettings,
  127. Permission.ReadAdministrator,
  128. Permission.CreateAdministrator,
  129. Permission.UpdateAdministrator,
  130. ],
  131. },
  132. },
  133. );
  134. expect(createRole.channels).toEqual([
  135. {
  136. id: 'T_2',
  137. code: 'second-channel',
  138. token: SECOND_CHANNEL_TOKEN,
  139. },
  140. ]);
  141. secondChannelAdminRole = createRole;
  142. });
  143. it('createAdministrator with second-channel-admin role', async () => {
  144. const { createAdministrator } = await adminClient.query<
  145. CreateAdministrator.Mutation,
  146. CreateAdministrator.Variables
  147. >(CREATE_ADMINISTRATOR, {
  148. input: {
  149. firstName: 'Admin',
  150. lastName: 'Two',
  151. emailAddress: 'admin2@test.com',
  152. password: 'test',
  153. roleIds: [secondChannelAdminRole.id],
  154. },
  155. });
  156. expect(createAdministrator.user.roles.map((r) => r.description)).toEqual(['second channel admin']);
  157. });
  158. it(
  159. 'cannot create role on channel for which admin does not have CreateAdministrator permission',
  160. assertThrowsWithMessage(async () => {
  161. await adminClient.asUserWithCredentials('admin2@test.com', 'test');
  162. await adminClient.query<CreateRole.Mutation, CreateRole.Variables>(CREATE_ROLE, {
  163. input: {
  164. description: 'read default channel catalog',
  165. code: 'read default channel catalog',
  166. channelIds: ['T_1'],
  167. permissions: [Permission.ReadCatalog],
  168. },
  169. });
  170. }, 'You are not currently authorized to perform this action'),
  171. );
  172. it('can create role on channel for which admin has CreateAdministrator permission', async () => {
  173. const { createRole } = await adminClient.query<CreateRole.Mutation, CreateRole.Variables>(
  174. CREATE_ROLE,
  175. {
  176. input: {
  177. description: 'read second channel catalog',
  178. code: 'read-second-channel-catalog',
  179. channelIds: ['T_2'],
  180. permissions: [Permission.ReadCatalog],
  181. },
  182. },
  183. );
  184. expect(createRole.channels).toEqual([
  185. {
  186. id: 'T_2',
  187. code: 'second-channel',
  188. token: SECOND_CHANNEL_TOKEN,
  189. },
  190. ]);
  191. });
  192. it('createRole with no channelId implicitly uses active channel', async () => {
  193. const { createRole } = await adminClient.query<CreateRole.Mutation, CreateRole.Variables>(
  194. CREATE_ROLE,
  195. {
  196. input: {
  197. description: 'update second channel catalog',
  198. code: 'update-second-channel-catalog',
  199. permissions: [Permission.UpdateCatalog],
  200. },
  201. },
  202. );
  203. expect(createRole.channels).toEqual([
  204. {
  205. id: 'T_2',
  206. code: 'second-channel',
  207. token: SECOND_CHANNEL_TOKEN,
  208. },
  209. ]);
  210. });
  211. describe('assigning Product to Channels', () => {
  212. let product1: GetProductWithVariants.Product;
  213. beforeAll(async () => {
  214. await adminClient.asSuperAdmin();
  215. await adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  216. await adminClient.query<CreateChannel.Mutation, CreateChannel.Variables>(CREATE_CHANNEL, {
  217. input: {
  218. code: 'third-channel',
  219. token: THIRD_CHANNEL_TOKEN,
  220. defaultLanguageCode: LanguageCode.en,
  221. currencyCode: CurrencyCode.GBP,
  222. pricesIncludeTax: true,
  223. defaultShippingZoneId: 'T_1',
  224. defaultTaxZoneId: 'T_1',
  225. },
  226. });
  227. const { product } = await adminClient.query<
  228. GetProductWithVariants.Query,
  229. GetProductWithVariants.Variables
  230. >(GET_PRODUCT_WITH_VARIANTS, {
  231. id: 'T_1',
  232. });
  233. product1 = product!;
  234. });
  235. it(
  236. 'throws if attempting to assign Product to channel to which the admin has no access',
  237. assertThrowsWithMessage(async () => {
  238. await adminClient.asUserWithCredentials('admin2@test.com', 'test');
  239. await adminClient.query<AssignProductsToChannel.Mutation, AssignProductsToChannel.Variables>(
  240. ASSIGN_PRODUCT_TO_CHANNEL,
  241. {
  242. input: {
  243. channelId: 'T_3',
  244. productIds: [product1.id],
  245. },
  246. },
  247. );
  248. }, 'You are not currently authorized to perform this action'),
  249. );
  250. it('assigns Product to Channel and applies price factor', async () => {
  251. const PRICE_FACTOR = 0.5;
  252. await adminClient.asSuperAdmin();
  253. const { assignProductsToChannel } = await adminClient.query<
  254. AssignProductsToChannel.Mutation,
  255. AssignProductsToChannel.Variables
  256. >(ASSIGN_PRODUCT_TO_CHANNEL, {
  257. input: {
  258. channelId: 'T_2',
  259. productIds: [product1.id],
  260. priceFactor: PRICE_FACTOR,
  261. },
  262. });
  263. expect(assignProductsToChannel[0].channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_2']);
  264. await adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  265. const { product } = await adminClient.query<
  266. GetProductWithVariants.Query,
  267. GetProductWithVariants.Variables
  268. >(GET_PRODUCT_WITH_VARIANTS, {
  269. id: product1.id,
  270. });
  271. expect(product!.variants.map((v) => v.price)).toEqual(
  272. product1.variants.map((v) => v.price * PRICE_FACTOR),
  273. );
  274. // Second Channel is configured to include taxes in price, so they should be the same.
  275. expect(product!.variants.map((v) => v.priceWithTax)).toEqual(
  276. product1.variants.map((v) => v.price * PRICE_FACTOR),
  277. );
  278. });
  279. it('does not assign Product to same channel twice', async () => {
  280. const { assignProductsToChannel } = await adminClient.query<
  281. AssignProductsToChannel.Mutation,
  282. AssignProductsToChannel.Variables
  283. >(ASSIGN_PRODUCT_TO_CHANNEL, {
  284. input: {
  285. channelId: 'T_2',
  286. productIds: [product1.id],
  287. },
  288. });
  289. expect(assignProductsToChannel[0].channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_2']);
  290. });
  291. it(
  292. 'throws if attempting to remove Product from default Channel',
  293. assertThrowsWithMessage(async () => {
  294. await adminClient.query<
  295. RemoveProductsFromChannel.Mutation,
  296. RemoveProductsFromChannel.Variables
  297. >(REMOVE_PRODUCT_FROM_CHANNEL, {
  298. input: {
  299. productIds: [product1.id],
  300. channelId: 'T_1',
  301. },
  302. });
  303. }, 'Products cannot be removed from the default Channel'),
  304. );
  305. it('removes Product from Channel', async () => {
  306. await adminClient.asSuperAdmin();
  307. await adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  308. const { removeProductsFromChannel } = await adminClient.query<
  309. RemoveProductsFromChannel.Mutation,
  310. RemoveProductsFromChannel.Variables
  311. >(REMOVE_PRODUCT_FROM_CHANNEL, {
  312. input: {
  313. productIds: [product1.id],
  314. channelId: 'T_2',
  315. },
  316. });
  317. expect(removeProductsFromChannel[0].channels.map((c) => c.id)).toEqual(['T_1']);
  318. });
  319. });
  320. describe('setting defaultLanguage', () => {
  321. it(
  322. 'throws if languageCode not in availableLanguages',
  323. assertThrowsWithMessage(async () => {
  324. await adminClient.query<UpdateChannel.Mutation, UpdateChannel.Variables>(UPDATE_CHANNEL, {
  325. input: {
  326. id: 'T_1',
  327. defaultLanguageCode: LanguageCode.zh,
  328. },
  329. });
  330. }, 'Language "zh" is not available. First enable it via GlobalSettings and try again.'),
  331. );
  332. it('allows setting to an available language', async () => {
  333. await adminClient.query<UpdateGlobalSettings.Mutation, UpdateGlobalSettings.Variables>(
  334. UPDATE_GLOBAL_SETTINGS,
  335. {
  336. input: {
  337. availableLanguages: [LanguageCode.en, LanguageCode.zh],
  338. },
  339. },
  340. );
  341. const { updateChannel } = await adminClient.query<
  342. UpdateChannel.Mutation,
  343. UpdateChannel.Variables
  344. >(UPDATE_CHANNEL, {
  345. input: {
  346. id: 'T_1',
  347. defaultLanguageCode: LanguageCode.zh,
  348. },
  349. });
  350. expect(updateChannel.defaultLanguageCode).toBe(LanguageCode.zh);
  351. });
  352. it(
  353. 'attempting to remove availableLanguage when used by a Channel throws',
  354. assertThrowsWithMessage(async () => {
  355. await adminClient.query<UpdateGlobalSettings.Mutation, UpdateGlobalSettings.Variables>(
  356. UPDATE_GLOBAL_SETTINGS,
  357. {
  358. input: {
  359. availableLanguages: [LanguageCode.en],
  360. },
  361. },
  362. );
  363. }, 'Cannot remove make language "zh" unavailable as it is used as the defaultLanguage by the channel "__default_channel__"'),
  364. );
  365. });
  366. it('deleteChannel', async () => {
  367. const PROD_ID = 'T_1';
  368. const { assignProductsToChannel } = await adminClient.query<
  369. AssignProductsToChannel.Mutation,
  370. AssignProductsToChannel.Variables
  371. >(ASSIGN_PRODUCT_TO_CHANNEL, {
  372. input: {
  373. channelId: 'T_2',
  374. productIds: [PROD_ID],
  375. },
  376. });
  377. expect(assignProductsToChannel[0].channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_2']);
  378. const { deleteChannel } = await adminClient.query<DeleteChannel.Mutation, DeleteChannel.Variables>(
  379. DELETE_CHANNEL,
  380. {
  381. id: 'T_2',
  382. },
  383. );
  384. expect(deleteChannel.result).toBe(DeletionResult.DELETED);
  385. const { channels } = await adminClient.query<GetChannels.Query>(GET_CHANNELS);
  386. expect(channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_3']);
  387. const { product } = await adminClient.query<
  388. GetProductWithVariants.Query,
  389. GetProductWithVariants.Variables
  390. >(GET_PRODUCT_WITH_VARIANTS, {
  391. id: PROD_ID,
  392. });
  393. expect(product!.channels.map((c) => c.id)).toEqual(['T_1']);
  394. });
  395. });
  396. const GET_CHANNELS = gql`
  397. query GetChannels {
  398. channels {
  399. id
  400. code
  401. token
  402. }
  403. }
  404. `;
  405. const UPDATE_CHANNEL = gql`
  406. mutation UpdateChannel($input: UpdateChannelInput!) {
  407. updateChannel(input: $input) {
  408. id
  409. code
  410. defaultLanguageCode
  411. currencyCode
  412. }
  413. }
  414. `;
  415. const DELETE_CHANNEL = gql`
  416. mutation DeleteChannel($id: ID!) {
  417. deleteChannel(id: $id) {
  418. message
  419. result
  420. }
  421. }
  422. `;
  423. const UPDATE_GLOBAL_SETTINGS = gql`
  424. mutation UpdateGlobalSettings($input: UpdateGlobalSettingsInput!) {
  425. updateGlobalSettings(input: $input) {
  426. id
  427. availableLanguages
  428. }
  429. }
  430. `;