zone.e2e-spec.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. import { Facet, LanguageCode, mergeConfig } from '@vendure/core';
  2. import { createTestEnvironment } from '@vendure/testing';
  3. import gql from 'graphql-tag';
  4. import path from 'path';
  5. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  6. import { initialData } from '../../../e2e-common/e2e-initial-data';
  7. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  8. import { ZONE_FRAGMENT } from './graphql/fragments';
  9. import * as Codegen from './graphql/generated-e2e-admin-types';
  10. import { DeletionResult } from './graphql/generated-e2e-admin-types';
  11. import { GET_COUNTRY_LIST, UPDATE_CHANNEL } from './graphql/shared-definitions';
  12. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  13. describe('Zone resolver', () => {
  14. const { server, adminClient } = createTestEnvironment(
  15. mergeConfig(testConfig(), {
  16. customFields: {
  17. Zone: [
  18. {
  19. name: 'relatedFacet',
  20. type: 'relation',
  21. entity: Facet,
  22. },
  23. ],
  24. },
  25. }),
  26. );
  27. let countries: Codegen.GetCountryListQuery['countries']['items'];
  28. let zones: Array<{ id: string; name: string }>;
  29. let oceania: { id: string; name: string };
  30. let pangaea: { id: string; name: string; members: any[] };
  31. beforeAll(async () => {
  32. await server.init({
  33. initialData,
  34. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  35. customerCount: 1,
  36. });
  37. await adminClient.asSuperAdmin();
  38. const result = await adminClient.query<Codegen.GetCountryListQuery>(GET_COUNTRY_LIST, {});
  39. countries = result.countries.items;
  40. }, TEST_SETUP_TIMEOUT_MS);
  41. afterAll(async () => {
  42. await server.destroy();
  43. });
  44. it('zones', async () => {
  45. const result = await adminClient.query<Codegen.GetZonesQuery>(GET_ZONE_LIST);
  46. expect(result.zones.items.length).toBe(5);
  47. zones = result.zones.items;
  48. oceania = zones[0];
  49. });
  50. it('zone', async () => {
  51. const result = await adminClient.query<Codegen.GetZoneQuery, Codegen.GetZoneQueryVariables>(
  52. GET_ZONE,
  53. {
  54. id: oceania.id,
  55. },
  56. );
  57. expect(result.zone!.name).toBe('Oceania');
  58. });
  59. it('zone.members field resolver', async () => {
  60. const { activeChannel } = await adminClient.query<Codegen.GetActiveChannelWithZoneMembersQuery>(
  61. GET_ACTIVE_CHANNEL_WITH_ZONE_MEMBERS,
  62. );
  63. expect(activeChannel.defaultShippingZone?.members.length).toBe(2);
  64. });
  65. it('updateZone', async () => {
  66. const result = await adminClient.query<
  67. Codegen.UpdateZoneMutation,
  68. Codegen.UpdateZoneMutationVariables
  69. >(UPDATE_ZONE, {
  70. input: {
  71. id: oceania.id,
  72. name: 'oceania2',
  73. },
  74. });
  75. expect(result.updateZone.name).toBe('oceania2');
  76. });
  77. it('createZone', async () => {
  78. const result = await adminClient.query<
  79. Codegen.CreateZoneMutation,
  80. Codegen.CreateZoneMutationVariables
  81. >(CREATE_ZONE, {
  82. input: {
  83. name: 'Pangaea',
  84. memberIds: [countries[0].id, countries[1].id],
  85. },
  86. });
  87. pangaea = result.createZone;
  88. expect(pangaea.name).toBe('Pangaea');
  89. expect(pangaea.members.map(m => m.name)).toEqual([countries[0].name, countries[1].name]);
  90. });
  91. it('addMembersToZone', async () => {
  92. const result = await adminClient.query<
  93. Codegen.AddMembersToZoneMutation,
  94. Codegen.AddMembersToZoneMutationVariables
  95. >(ADD_MEMBERS_TO_ZONE, {
  96. zoneId: oceania.id,
  97. memberIds: [countries[2].id, countries[3].id],
  98. });
  99. expect(!!result.addMembersToZone.members.find(m => m.name === countries[2].name)).toBe(true);
  100. expect(!!result.addMembersToZone.members.find(m => m.name === countries[3].name)).toBe(true);
  101. });
  102. it('removeMembersFromZone', async () => {
  103. const result = await adminClient.query<
  104. Codegen.RemoveMembersFromZoneMutation,
  105. Codegen.RemoveMembersFromZoneMutationVariables
  106. >(REMOVE_MEMBERS_FROM_ZONE, {
  107. zoneId: oceania.id,
  108. memberIds: [countries[0].id, countries[2].id],
  109. });
  110. expect(!!result.removeMembersFromZone.members.find(m => m.name === countries[0].name)).toBe(false);
  111. expect(!!result.removeMembersFromZone.members.find(m => m.name === countries[2].name)).toBe(false);
  112. expect(!!result.removeMembersFromZone.members.find(m => m.name === countries[3].name)).toBe(true);
  113. });
  114. describe('deletion', () => {
  115. it('deletes Zone not used in any TaxRate', async () => {
  116. const result1 = await adminClient.query<
  117. Codegen.DeleteZoneMutation,
  118. Codegen.DeleteZoneMutationVariables
  119. >(DELETE_ZONE, {
  120. id: pangaea.id,
  121. });
  122. expect(result1.deleteZone).toEqual({
  123. result: DeletionResult.DELETED,
  124. message: '',
  125. });
  126. const result2 = await adminClient.query<Codegen.GetZonesQuery>(GET_ZONE_LIST);
  127. expect(result2.zones.items.find(c => c.id === pangaea.id)).toBeUndefined();
  128. });
  129. it('does not delete Zone that is used in one or more TaxRates', async () => {
  130. const result1 = await adminClient.query<
  131. Codegen.DeleteZoneMutation,
  132. Codegen.DeleteZoneMutationVariables
  133. >(DELETE_ZONE, {
  134. id: oceania.id,
  135. });
  136. expect(result1.deleteZone).toEqual({
  137. result: DeletionResult.NOT_DELETED,
  138. message:
  139. 'The selected Zone cannot be deleted as it is used in the following ' +
  140. 'TaxRates: Standard Tax Oceania, Reduced Tax Oceania, Zero Tax Oceania',
  141. });
  142. const result2 = await adminClient.query<Codegen.GetZonesQuery>(GET_ZONE_LIST);
  143. expect(result2.zones.items.find(c => c.id === oceania.id)).not.toBeUndefined();
  144. });
  145. it('does not delete Zone that is used as a Channel defaultTaxZone', async () => {
  146. await adminClient.query<Codegen.UpdateChannelMutation, Codegen.UpdateChannelMutationVariables>(
  147. UPDATE_CHANNEL,
  148. {
  149. input: {
  150. id: 'T_1',
  151. defaultTaxZoneId: oceania.id,
  152. },
  153. },
  154. );
  155. const result1 = await adminClient.query<
  156. Codegen.DeleteZoneMutation,
  157. Codegen.DeleteZoneMutationVariables
  158. >(DELETE_ZONE, {
  159. id: oceania.id,
  160. });
  161. expect(result1.deleteZone).toEqual({
  162. result: DeletionResult.NOT_DELETED,
  163. message:
  164. 'The selected Zone cannot be deleted as it used as a default in the following Channels: ' +
  165. '__default_channel__',
  166. });
  167. const result2 = await adminClient.query<Codegen.GetZonesQuery>(GET_ZONE_LIST);
  168. expect(result2.zones.items.find(c => c.id === oceania.id)).not.toBeUndefined();
  169. });
  170. it('does not delete Zone that is used as a Channel defaultShippingZone', async () => {
  171. await adminClient.query<Codegen.UpdateChannelMutation, Codegen.UpdateChannelMutationVariables>(
  172. UPDATE_CHANNEL,
  173. {
  174. input: {
  175. id: 'T_1',
  176. defaultTaxZoneId: 'T_1',
  177. defaultShippingZoneId: oceania.id,
  178. },
  179. },
  180. );
  181. const result1 = await adminClient.query<
  182. Codegen.DeleteZoneMutation,
  183. Codegen.DeleteZoneMutationVariables
  184. >(DELETE_ZONE, {
  185. id: oceania.id,
  186. });
  187. expect(result1.deleteZone).toEqual({
  188. result: DeletionResult.NOT_DELETED,
  189. message:
  190. 'The selected Zone cannot be deleted as it used as a default in the following Channels: ' +
  191. '__default_channel__',
  192. });
  193. const result2 = await adminClient.query<Codegen.GetZonesQuery>(GET_ZONE_LIST);
  194. expect(result2.zones.items.find(c => c.id === oceania.id)).not.toBeUndefined();
  195. });
  196. });
  197. describe('Zone custom fields', () => {
  198. let testFacet: Codegen.CreateFacetMutation['createFacet'];
  199. // Create a target entity (Facet) to link the Zone to
  200. it('create a target Facet', async () => {
  201. const result = await adminClient.query<
  202. Codegen.CreateFacetMutation,
  203. Codegen.CreateFacetMutationVariables
  204. >(CREATE_FACET_WITH_VALUE, {
  205. input: {
  206. code: 'test-relation-facet',
  207. isPrivate: false,
  208. translations: [{ languageCode: LanguageCode.en, name: 'Test Relation Facet' }],
  209. },
  210. });
  211. testFacet = result.createFacet;
  212. expect(testFacet.name).toBe('Test Relation Facet');
  213. });
  214. // Test createZone with a custom relation field
  215. it('createZone persists custom relation field', async () => {
  216. const input: Codegen.CreateZoneInput = {
  217. name: 'Zone with Custom Relation',
  218. memberIds: [],
  219. customFields: {
  220. relatedFacetId: testFacet.id,
  221. },
  222. };
  223. const result = await adminClient.query<
  224. CreateZoneMutationWithCF,
  225. Codegen.CreateZoneMutationVariables
  226. >(gql(CREATE_ZONE_WITH_CF), { input });
  227. // Verify the return value
  228. expect(result.createZone.customFields.relatedFacet.id).toBe(testFacet.id);
  229. // Verify by querying it again from the database
  230. const result2 = await adminClient.query<GetZoneQueryWithCF, Codegen.GetZoneQueryVariables>(
  231. gql(GET_ZONE_WITH_CUSTOM_FIELDS),
  232. { id: result.createZone.id },
  233. );
  234. expect(result2.zone.customFields.relatedFacet.id).toBe(testFacet.id);
  235. });
  236. // Test updateZone with a custom relation field
  237. it('updateZone persists custom relation field', async () => {
  238. const result = await adminClient.query<
  239. UpdateZoneMutationWithCF,
  240. Codegen.UpdateZoneMutationVariables
  241. >(gql(UPDATE_ZONE_WITH_CF), {
  242. input: {
  243. id: zones[1].id,
  244. customFields: {
  245. relatedFacetId: testFacet.id,
  246. },
  247. },
  248. });
  249. // Verify the return value
  250. expect(result.updateZone.customFields.relatedFacet.id).toBe(testFacet.id);
  251. // Verify by querying it again from the database
  252. const result2 = await adminClient.query<GetZoneQueryWithCF, Codegen.GetZoneQueryVariables>(
  253. gql(GET_ZONE_WITH_CUSTOM_FIELDS),
  254. { id: zones[1].id },
  255. );
  256. expect(result2.zone.customFields.relatedFacet.id).toBe(testFacet.id);
  257. });
  258. });
  259. });
  260. type ZoneWithCustomFields = Omit<Codegen.Zone, 'customFields'> & {
  261. customFields: {
  262. relatedFacet: {
  263. id: string;
  264. };
  265. };
  266. };
  267. type CreateZoneMutationWithCF = Omit<Codegen.CreateZoneMutation, 'createZone'> & {
  268. createZone: ZoneWithCustomFields;
  269. };
  270. type UpdateZoneMutationWithCF = Omit<Codegen.UpdateZoneMutation, 'updateZone'> & {
  271. updateZone: ZoneWithCustomFields;
  272. };
  273. type GetZoneQueryWithCF = Omit<Codegen.GetZoneQuery, 'zone'> & {
  274. zone: ZoneWithCustomFields;
  275. };
  276. const CREATE_FACET_WITH_VALUE = gql`
  277. mutation CreateFacetWithValue($input: CreateFacetInput!) {
  278. createFacet(input: $input) {
  279. id
  280. name
  281. }
  282. }
  283. `;
  284. // A new fragment to include the custom fields
  285. const ZONE_CUSTOM_FIELDS_FRAGMENT = `
  286. fragment ZoneCustomFields on Zone {
  287. customFields {
  288. relatedFacet {
  289. id
  290. }
  291. }
  292. }
  293. `;
  294. // A new mutation to create a Zone with custom fields
  295. const CREATE_ZONE_WITH_CF = `
  296. mutation CreateZoneWithCF($input: CreateZoneInput!) {
  297. createZone(input: $input) {
  298. id
  299. name
  300. ...ZoneCustomFields
  301. }
  302. }
  303. ${ZONE_CUSTOM_FIELDS_FRAGMENT}
  304. `;
  305. // A new mutation to update a Zone with custom fields
  306. const UPDATE_ZONE_WITH_CF = `
  307. mutation UpdateZoneWithCF($input: UpdateZoneInput!) {
  308. updateZone(input: $input) {
  309. id
  310. name
  311. ...ZoneCustomFields
  312. }
  313. }
  314. ${ZONE_CUSTOM_FIELDS_FRAGMENT}
  315. `;
  316. // A new query to fetch the Zone with its custom fields
  317. const GET_ZONE_WITH_CUSTOM_FIELDS = `
  318. query GetZoneWithCustomFields($id: ID!) {
  319. zone(id: $id) {
  320. id
  321. name
  322. ...ZoneCustomFields
  323. }
  324. }
  325. ${ZONE_CUSTOM_FIELDS_FRAGMENT}
  326. `;
  327. const DELETE_ZONE = gql`
  328. mutation DeleteZone($id: ID!) {
  329. deleteZone(id: $id) {
  330. result
  331. message
  332. }
  333. }
  334. `;
  335. const GET_ZONE_LIST = gql`
  336. query GetZones($options: ZoneListOptions) {
  337. zones(options: $options) {
  338. items {
  339. id
  340. name
  341. }
  342. totalItems
  343. }
  344. }
  345. `;
  346. export const GET_ZONE = gql`
  347. query GetZone($id: ID!) {
  348. zone(id: $id) {
  349. ...Zone
  350. }
  351. }
  352. ${ZONE_FRAGMENT}
  353. `;
  354. export const GET_ACTIVE_CHANNEL_WITH_ZONE_MEMBERS = gql`
  355. query GetActiveChannelWithZoneMembers {
  356. activeChannel {
  357. id
  358. defaultShippingZone {
  359. id
  360. members {
  361. name
  362. }
  363. }
  364. }
  365. }
  366. `;
  367. export const CREATE_ZONE = gql`
  368. mutation CreateZone($input: CreateZoneInput!) {
  369. createZone(input: $input) {
  370. ...Zone
  371. }
  372. }
  373. ${ZONE_FRAGMENT}
  374. `;
  375. export const UPDATE_ZONE = gql`
  376. mutation UpdateZone($input: UpdateZoneInput!) {
  377. updateZone(input: $input) {
  378. ...Zone
  379. }
  380. }
  381. ${ZONE_FRAGMENT}
  382. `;
  383. export const ADD_MEMBERS_TO_ZONE = gql`
  384. mutation AddMembersToZone($zoneId: ID!, $memberIds: [ID!]!) {
  385. addMembersToZone(zoneId: $zoneId, memberIds: $memberIds) {
  386. ...Zone
  387. }
  388. }
  389. ${ZONE_FRAGMENT}
  390. `;
  391. export const REMOVE_MEMBERS_FROM_ZONE = gql`
  392. mutation RemoveMembersFromZone($zoneId: ID!, $memberIds: [ID!]!) {
  393. removeMembersFromZone(zoneId: $zoneId, memberIds: $memberIds) {
  394. ...Zone
  395. }
  396. }
  397. ${ZONE_FRAGMENT}
  398. `;