channel.e2e-spec.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import { DEFAULT_CHANNEL_CODE } from '@vendure/common/lib/shared-constants';
  3. import {
  4. createErrorResultGuard,
  5. createTestEnvironment,
  6. E2E_DEFAULT_CHANNEL_TOKEN,
  7. ErrorResultGuard,
  8. } from '@vendure/testing';
  9. import gql from 'graphql-tag';
  10. import path from 'path';
  11. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  12. import { initialData } from '../../../e2e-common/e2e-initial-data';
  13. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  14. import * as Codegen from './graphql/generated-e2e-admin-types';
  15. import {
  16. CurrencyCode,
  17. DeletionResult,
  18. ErrorCode,
  19. LanguageCode,
  20. Permission,
  21. } from './graphql/generated-e2e-admin-types';
  22. import {
  23. ASSIGN_PRODUCT_TO_CHANNEL,
  24. CREATE_ADMINISTRATOR,
  25. CREATE_CHANNEL,
  26. CREATE_ROLE,
  27. GET_CHANNELS,
  28. GET_CUSTOMER_LIST,
  29. GET_PRODUCT_LIST,
  30. GET_PRODUCT_WITH_VARIANTS,
  31. ME,
  32. UPDATE_CHANNEL,
  33. } from './graphql/shared-definitions';
  34. import { GET_ACTIVE_ORDER } from './graphql/shop-definitions';
  35. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  36. describe('Channels', () => {
  37. const { server, adminClient, shopClient } = createTestEnvironment(testConfig());
  38. const SECOND_CHANNEL_TOKEN = 'second_channel_token';
  39. let secondChannelAdminRole: Codegen.CreateRoleMutation['createRole'];
  40. let customerUser: Codegen.GetCustomerListQuery['customers']['items'][number];
  41. const channelGuard: ErrorResultGuard<Codegen.ChannelFragment> =
  42. createErrorResultGuard<Codegen.ChannelFragment>(input => !!input.defaultLanguageCode);
  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<
  51. Codegen.GetCustomerListQuery,
  52. Codegen.GetCustomerListQueryVariables
  53. >(GET_CUSTOMER_LIST, {
  54. options: { take: 1 },
  55. });
  56. customerUser = customers.items[0];
  57. }, TEST_SETUP_TIMEOUT_MS);
  58. afterAll(async () => {
  59. await server.destroy();
  60. });
  61. it('createChannel returns error result defaultLanguageCode not available', async () => {
  62. const { createChannel } = await adminClient.query<
  63. Codegen.CreateChannelMutation,
  64. Codegen.CreateChannelMutationVariables
  65. >(CREATE_CHANNEL, {
  66. input: {
  67. code: 'second-channel',
  68. token: SECOND_CHANNEL_TOKEN,
  69. defaultLanguageCode: LanguageCode.zh,
  70. currencyCode: CurrencyCode.GBP,
  71. pricesIncludeTax: true,
  72. defaultShippingZoneId: 'T_1',
  73. defaultTaxZoneId: 'T_1',
  74. },
  75. });
  76. channelGuard.assertErrorResult(createChannel);
  77. expect(createChannel.message).toBe(
  78. 'Language "zh" is not available. First enable it via GlobalSettings and try again',
  79. );
  80. expect(createChannel.errorCode).toBe(ErrorCode.LANGUAGE_NOT_AVAILABLE_ERROR);
  81. expect(createChannel.languageCode).toBe('zh');
  82. });
  83. it('create a new Channel', async () => {
  84. const { createChannel } = await adminClient.query<
  85. Codegen.CreateChannelMutation,
  86. Codegen.CreateChannelMutationVariables
  87. >(CREATE_CHANNEL, {
  88. input: {
  89. code: 'second-channel',
  90. token: SECOND_CHANNEL_TOKEN,
  91. defaultLanguageCode: LanguageCode.en,
  92. currencyCode: CurrencyCode.GBP,
  93. pricesIncludeTax: true,
  94. defaultShippingZoneId: 'T_1',
  95. defaultTaxZoneId: 'T_1',
  96. },
  97. });
  98. channelGuard.assertSuccess(createChannel);
  99. expect(createChannel).toEqual({
  100. id: 'T_2',
  101. code: 'second-channel',
  102. token: SECOND_CHANNEL_TOKEN,
  103. availableCurrencyCodes: ['GBP'],
  104. currencyCode: 'GBP',
  105. defaultCurrencyCode: 'GBP',
  106. defaultLanguageCode: 'en',
  107. defaultShippingZone: {
  108. id: 'T_1',
  109. },
  110. defaultTaxZone: {
  111. id: 'T_1',
  112. },
  113. pricesIncludeTax: true,
  114. });
  115. });
  116. // it('update currencyCode', async () => {
  117. // const { updateChannel } = await adminClient.query<
  118. // Codegen.UpdateChannelMutation,
  119. // Codegen.UpdateChannelMutationVariables
  120. // >(UPDATE_CHANNEL, {
  121. // input: {
  122. // id: 'T_1',
  123. // currencyCode: CurrencyCode.MYR,
  124. // },
  125. // });
  126. // channelGuard.assertSuccess(updateChannel);
  127. // expect(updateChannel.currencyCode).toBe('MYR');
  128. // });
  129. it('superadmin has all permissions on new channel', async () => {
  130. const { me } = await adminClient.query<Codegen.MeQuery>(ME);
  131. expect(me!.channels.length).toBe(2);
  132. const secondChannelData = me!.channels.find(c => c.token === SECOND_CHANNEL_TOKEN);
  133. const nonOwnerPermissions = Object.values(Permission).filter(
  134. p => p !== Permission.Owner && p !== Permission.Public,
  135. );
  136. expect(secondChannelData!.permissions.sort()).toEqual(nonOwnerPermissions);
  137. });
  138. it('customer has Authenticated permission on new channel', async () => {
  139. await shopClient.asUserWithCredentials(customerUser.emailAddress, 'test');
  140. const { me } = await shopClient.query<Codegen.MeQuery>(ME);
  141. expect(me!.channels.length).toBe(2);
  142. const secondChannelData = me!.channels.find(c => c.token === SECOND_CHANNEL_TOKEN);
  143. expect(me!.channels).toEqual([
  144. {
  145. code: DEFAULT_CHANNEL_CODE,
  146. permissions: ['Authenticated'],
  147. token: E2E_DEFAULT_CHANNEL_TOKEN,
  148. },
  149. {
  150. code: 'second-channel',
  151. permissions: ['Authenticated'],
  152. token: SECOND_CHANNEL_TOKEN,
  153. },
  154. ]);
  155. });
  156. it('createRole on second Channel', async () => {
  157. const { createRole } = await adminClient.query<
  158. Codegen.CreateRoleMutation,
  159. Codegen.CreateRoleMutationVariables
  160. >(CREATE_ROLE, {
  161. input: {
  162. description: 'second channel admin',
  163. code: 'second-channel-admin',
  164. channelIds: ['T_2'],
  165. permissions: [
  166. Permission.ReadCatalog,
  167. Permission.ReadSettings,
  168. Permission.ReadAdministrator,
  169. Permission.CreateAdministrator,
  170. Permission.UpdateAdministrator,
  171. ],
  172. },
  173. });
  174. expect(createRole.channels).toEqual([
  175. {
  176. id: 'T_2',
  177. code: 'second-channel',
  178. token: SECOND_CHANNEL_TOKEN,
  179. },
  180. ]);
  181. secondChannelAdminRole = createRole;
  182. });
  183. it('createAdministrator with second-channel-admin role', async () => {
  184. const { createAdministrator } = await adminClient.query<
  185. Codegen.CreateAdministratorMutation,
  186. Codegen.CreateAdministratorMutationVariables
  187. >(CREATE_ADMINISTRATOR, {
  188. input: {
  189. firstName: 'Admin',
  190. lastName: 'Two',
  191. emailAddress: 'admin2@test.com',
  192. password: 'test',
  193. roleIds: [secondChannelAdminRole.id],
  194. },
  195. });
  196. expect(createAdministrator.user.roles.map(r => r.description)).toEqual(['second channel admin']);
  197. });
  198. it(
  199. 'cannot create role on channel for which admin does not have CreateAdministrator permission',
  200. assertThrowsWithMessage(async () => {
  201. await adminClient.asUserWithCredentials('admin2@test.com', 'test');
  202. await adminClient.query<Codegen.CreateRoleMutation, Codegen.CreateRoleMutationVariables>(
  203. CREATE_ROLE,
  204. {
  205. input: {
  206. description: 'read default channel catalog',
  207. code: 'read default channel catalog',
  208. channelIds: ['T_1'],
  209. permissions: [Permission.ReadCatalog],
  210. },
  211. },
  212. );
  213. }, 'You are not currently authorized to perform this action'),
  214. );
  215. it('can create role on channel for which admin has CreateAdministrator permission', async () => {
  216. const { createRole } = await adminClient.query<
  217. Codegen.CreateRoleMutation,
  218. Codegen.CreateRoleMutationVariables
  219. >(CREATE_ROLE, {
  220. input: {
  221. description: 'read second channel catalog',
  222. code: 'read-second-channel-catalog',
  223. channelIds: ['T_2'],
  224. permissions: [Permission.ReadCatalog],
  225. },
  226. });
  227. expect(createRole.channels).toEqual([
  228. {
  229. id: 'T_2',
  230. code: 'second-channel',
  231. token: SECOND_CHANNEL_TOKEN,
  232. },
  233. ]);
  234. });
  235. it('createRole with no channelId implicitly uses active channel', async () => {
  236. await adminClient.asSuperAdmin();
  237. const { createRole } = await adminClient.query<
  238. Codegen.CreateRoleMutation,
  239. Codegen.CreateRoleMutationVariables
  240. >(CREATE_ROLE, {
  241. input: {
  242. description: 'update second channel catalog',
  243. code: 'update-second-channel-catalog',
  244. permissions: [Permission.UpdateCatalog],
  245. },
  246. });
  247. expect(createRole.channels).toEqual([
  248. {
  249. id: 'T_2',
  250. code: 'second-channel',
  251. token: SECOND_CHANNEL_TOKEN,
  252. },
  253. ]);
  254. });
  255. describe('setting defaultLanguage', () => {
  256. it('returns error result if languageCode not in availableLanguages', async () => {
  257. await adminClient.asSuperAdmin();
  258. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  259. const { updateChannel } = await adminClient.query<
  260. Codegen.UpdateChannelMutation,
  261. Codegen.UpdateChannelMutationVariables
  262. >(UPDATE_CHANNEL, {
  263. input: {
  264. id: 'T_1',
  265. defaultLanguageCode: LanguageCode.zh,
  266. },
  267. });
  268. channelGuard.assertErrorResult(updateChannel);
  269. expect(updateChannel.message).toBe(
  270. 'Language "zh" is not available. First enable it via GlobalSettings and try again',
  271. );
  272. expect(updateChannel.errorCode).toBe(ErrorCode.LANGUAGE_NOT_AVAILABLE_ERROR);
  273. expect(updateChannel.languageCode).toBe('zh');
  274. });
  275. it('allows setting to an available language', async () => {
  276. await adminClient.asSuperAdmin();
  277. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  278. await adminClient.query<
  279. Codegen.UpdateGlobalLanguagesMutation,
  280. Codegen.UpdateGlobalLanguagesMutationVariables
  281. >(UPDATE_GLOBAL_LANGUAGES, {
  282. input: {
  283. availableLanguages: [LanguageCode.en, LanguageCode.zh],
  284. },
  285. });
  286. const { updateChannel } = await adminClient.query<
  287. Codegen.UpdateChannelMutation,
  288. Codegen.UpdateChannelMutationVariables
  289. >(UPDATE_CHANNEL, {
  290. input: {
  291. id: 'T_1',
  292. defaultLanguageCode: LanguageCode.zh,
  293. },
  294. });
  295. channelGuard.assertSuccess(updateChannel);
  296. expect(updateChannel.defaultLanguageCode).toBe(LanguageCode.zh);
  297. });
  298. });
  299. it('deleteChannel', async () => {
  300. const PROD_ID = 'T_1';
  301. await adminClient.asSuperAdmin();
  302. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  303. const { assignProductsToChannel } = await adminClient.query<
  304. Codegen.AssignProductsToChannelMutation,
  305. Codegen.AssignProductsToChannelMutationVariables
  306. >(ASSIGN_PRODUCT_TO_CHANNEL, {
  307. input: {
  308. channelId: 'T_2',
  309. productIds: [PROD_ID],
  310. },
  311. });
  312. expect(assignProductsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
  313. // create a Session on the Channel to be deleted to ensure it gets cleaned up
  314. shopClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  315. await shopClient.query(GET_ACTIVE_ORDER);
  316. const { deleteChannel } = await adminClient.query<
  317. Codegen.DeleteChannelMutation,
  318. Codegen.DeleteChannelMutationVariables
  319. >(DELETE_CHANNEL, {
  320. id: 'T_2',
  321. });
  322. expect(deleteChannel.result).toBe(DeletionResult.DELETED);
  323. const { channels } = await adminClient.query<Codegen.GetChannelsQuery>(GET_CHANNELS);
  324. expect(channels.items.map(c => c.id).sort()).toEqual(['T_1']);
  325. const { product } = await adminClient.query<
  326. Codegen.GetProductWithVariantsQuery,
  327. Codegen.GetProductWithVariantsQueryVariables
  328. >(GET_PRODUCT_WITH_VARIANTS, {
  329. id: PROD_ID,
  330. });
  331. expect(product!.channels.map(c => c.id)).toEqual(['T_1']);
  332. });
  333. describe('currencyCode support', () => {
  334. beforeAll(async () => {
  335. await adminClient.asSuperAdmin();
  336. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  337. });
  338. it('initial currencyCode values', async () => {
  339. const { channel } = await adminClient.query<
  340. Codegen.GetChannelQuery,
  341. Codegen.GetChannelQueryVariables
  342. >(GET_CHANNEL, {
  343. id: 'T_1',
  344. });
  345. expect(channel?.defaultCurrencyCode).toBe('USD');
  346. expect(channel?.availableCurrencyCodes).toEqual(['USD']);
  347. });
  348. it('setting defaultCurrencyCode adds it to availableCurrencyCodes', async () => {
  349. const { updateChannel } = await adminClient.query<
  350. Codegen.UpdateChannelMutation,
  351. Codegen.UpdateChannelMutationVariables
  352. >(UPDATE_CHANNEL, {
  353. input: {
  354. id: 'T_1',
  355. defaultCurrencyCode: CurrencyCode.MYR,
  356. },
  357. });
  358. channelGuard.assertSuccess(updateChannel);
  359. expect(updateChannel.defaultCurrencyCode).toBe('MYR');
  360. expect(updateChannel.currencyCode).toBe('MYR');
  361. expect(updateChannel.availableCurrencyCodes).toEqual(['USD', 'MYR']);
  362. });
  363. it('setting defaultCurrencyCode adds it to availableCurrencyCodes 2', async () => {
  364. // As above, but this time we set the availableCurrencyCodes explicitly
  365. // to exclude the defaultCurrencyCode
  366. const { updateChannel } = await adminClient.query<
  367. Codegen.UpdateChannelMutation,
  368. Codegen.UpdateChannelMutationVariables
  369. >(UPDATE_CHANNEL, {
  370. input: {
  371. id: 'T_1',
  372. defaultCurrencyCode: CurrencyCode.AUD,
  373. availableCurrencyCodes: [CurrencyCode.GBP],
  374. },
  375. });
  376. channelGuard.assertSuccess(updateChannel);
  377. expect(updateChannel.defaultCurrencyCode).toBe('AUD');
  378. expect(updateChannel.currencyCode).toBe('AUD');
  379. expect(updateChannel.availableCurrencyCodes).toEqual(['GBP', 'AUD']);
  380. });
  381. it(
  382. 'cannot remove the defaultCurrencyCode from availableCurrencyCodes',
  383. assertThrowsWithMessage(async () => {
  384. await adminClient.query<
  385. Codegen.UpdateChannelMutation,
  386. Codegen.UpdateChannelMutationVariables
  387. >(UPDATE_CHANNEL, {
  388. input: {
  389. id: 'T_1',
  390. availableCurrencyCodes: [CurrencyCode.GBP],
  391. },
  392. });
  393. }, 'availableCurrencyCodes must include the defaultCurrencyCode (AUD)'),
  394. );
  395. it(
  396. 'specifying an unsupported currencyCode throws',
  397. assertThrowsWithMessage(async () => {
  398. await adminClient.query<Codegen.GetProductListQuery, Codegen.GetProductListQueryVariables>(
  399. GET_PRODUCT_LIST,
  400. {
  401. options: {
  402. take: 1,
  403. },
  404. },
  405. { currencyCode: 'JPY' },
  406. );
  407. }, 'The currency "JPY" is not available in the current Channel'),
  408. );
  409. });
  410. });
  411. const DELETE_CHANNEL = gql`
  412. mutation DeleteChannel($id: ID!) {
  413. deleteChannel(id: $id) {
  414. message
  415. result
  416. }
  417. }
  418. `;
  419. const GET_CHANNEL = gql`
  420. query GetChannel($id: ID!) {
  421. channel(id: $id) {
  422. id
  423. code
  424. token
  425. defaultCurrencyCode
  426. availableCurrencyCodes
  427. defaultLanguageCode
  428. availableLanguageCodes
  429. outOfStockThreshold
  430. pricesIncludeTax
  431. }
  432. }
  433. `;
  434. const UPDATE_GLOBAL_LANGUAGES = gql`
  435. mutation UpdateGlobalLanguages($input: UpdateGlobalSettingsInput!) {
  436. updateGlobalSettings(input: $input) {
  437. ... on GlobalSettings {
  438. id
  439. availableLanguages
  440. }
  441. }
  442. }
  443. `;