asset.e2e-spec.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. /* tslint:disable:no-non-null-assertion */
  2. import { omit } from '@vendure/common/lib/omit';
  3. import { mergeConfig } from '@vendure/core';
  4. import { createTestEnvironment } from '@vendure/testing';
  5. import gql from 'graphql-tag';
  6. import path from 'path';
  7. import { initialData } from '../../../e2e-common/e2e-initial-data';
  8. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  9. import { ASSET_FRAGMENT } from './graphql/fragments';
  10. import {
  11. CreateAssets,
  12. DeleteAsset,
  13. DeletionResult,
  14. GetAsset,
  15. GetAssetFragmentFirst,
  16. GetAssetList,
  17. GetProductWithVariants,
  18. SortOrder,
  19. UpdateAsset,
  20. } from './graphql/generated-e2e-admin-types';
  21. import {
  22. DELETE_ASSET,
  23. GET_ASSET_LIST,
  24. GET_PRODUCT_WITH_VARIANTS,
  25. UPDATE_ASSET,
  26. } from './graphql/shared-definitions';
  27. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  28. describe('Asset resolver', () => {
  29. const { server, adminClient } = createTestEnvironment(
  30. mergeConfig(testConfig, {
  31. assetOptions: {
  32. permittedFileTypes: ['image/*', '.pdf'],
  33. },
  34. }),
  35. );
  36. let firstAssetId: string;
  37. let createdAssetId: string;
  38. beforeAll(async () => {
  39. await server.init({
  40. initialData,
  41. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  42. customerCount: 1,
  43. });
  44. await adminClient.asSuperAdmin();
  45. }, TEST_SETUP_TIMEOUT_MS);
  46. afterAll(async () => {
  47. await server.destroy();
  48. });
  49. it('assets', async () => {
  50. const { assets } = await adminClient.query<GetAssetList.Query, GetAssetList.Variables>(
  51. GET_ASSET_LIST,
  52. {
  53. options: {
  54. sort: {
  55. name: SortOrder.ASC,
  56. },
  57. },
  58. },
  59. );
  60. expect(assets.totalItems).toBe(4);
  61. expect(assets.items.map(a => omit(a, ['id']))).toEqual([
  62. {
  63. fileSize: 1680,
  64. mimeType: 'image/jpeg',
  65. name: 'alexandru-acea-686569-unsplash.jpg',
  66. preview: 'test-url/test-assets/alexandru-acea-686569-unsplash__preview.jpg',
  67. source: 'test-url/test-assets/alexandru-acea-686569-unsplash.jpg',
  68. type: 'IMAGE',
  69. },
  70. {
  71. fileSize: 1680,
  72. mimeType: 'image/jpeg',
  73. name: 'derick-david-409858-unsplash.jpg',
  74. preview: 'test-url/test-assets/derick-david-409858-unsplash__preview.jpg',
  75. source: 'test-url/test-assets/derick-david-409858-unsplash.jpg',
  76. type: 'IMAGE',
  77. },
  78. {
  79. fileSize: 1680,
  80. mimeType: 'image/jpeg',
  81. name: 'florian-olivo-1166419-unsplash.jpg',
  82. preview: 'test-url/test-assets/florian-olivo-1166419-unsplash__preview.jpg',
  83. source: 'test-url/test-assets/florian-olivo-1166419-unsplash.jpg',
  84. type: 'IMAGE',
  85. },
  86. {
  87. fileSize: 1680,
  88. mimeType: 'image/jpeg',
  89. name: 'vincent-botta-736919-unsplash.jpg',
  90. preview: 'test-url/test-assets/vincent-botta-736919-unsplash__preview.jpg',
  91. source: 'test-url/test-assets/vincent-botta-736919-unsplash.jpg',
  92. type: 'IMAGE',
  93. },
  94. ]);
  95. firstAssetId = assets.items[0].id;
  96. });
  97. it('asset', async () => {
  98. const { asset } = await adminClient.query<GetAsset.Query, GetAsset.Variables>(GET_ASSET, {
  99. id: firstAssetId,
  100. });
  101. expect(asset).toEqual({
  102. fileSize: 1680,
  103. height: 48,
  104. id: firstAssetId,
  105. mimeType: 'image/jpeg',
  106. name: 'alexandru-acea-686569-unsplash.jpg',
  107. preview: 'test-url/test-assets/alexandru-acea-686569-unsplash__preview.jpg',
  108. source: 'test-url/test-assets/alexandru-acea-686569-unsplash.jpg',
  109. type: 'IMAGE',
  110. width: 48,
  111. });
  112. });
  113. /**
  114. * https://github.com/vendure-ecommerce/vendure/issues/459
  115. */
  116. it('transforms URL when fragment defined before query (GH issue #459)', async () => {
  117. const { asset } = await adminClient.query<
  118. GetAssetFragmentFirst.Query,
  119. GetAssetFragmentFirst.Variables
  120. >(GET_ASSET_FRAGMENT_FIRST, {
  121. id: firstAssetId,
  122. });
  123. expect(asset?.preview).toBe('test-url/test-assets/alexandru-acea-686569-unsplash__preview.jpg');
  124. });
  125. describe('createAssets', () => {
  126. it('permitted types by mime type', async () => {
  127. const filesToUpload = [
  128. path.join(__dirname, 'fixtures/assets/pps1.jpg'),
  129. path.join(__dirname, 'fixtures/assets/pps2.jpg'),
  130. ];
  131. const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
  132. mutation: CREATE_ASSETS,
  133. filePaths: filesToUpload,
  134. mapVariables: filePaths => ({
  135. input: filePaths.map(p => ({ file: null })),
  136. }),
  137. });
  138. expect(createAssets.map(a => omit(a, ['id'])).sort((a, b) => (a.name < b.name ? -1 : 1))).toEqual(
  139. [
  140. {
  141. fileSize: 1680,
  142. focalPoint: null,
  143. mimeType: 'image/jpeg',
  144. name: 'pps1.jpg',
  145. preview: 'test-url/test-assets/pps1__preview.jpg',
  146. source: 'test-url/test-assets/pps1.jpg',
  147. type: 'IMAGE',
  148. },
  149. {
  150. fileSize: 1680,
  151. focalPoint: null,
  152. mimeType: 'image/jpeg',
  153. name: 'pps2.jpg',
  154. preview: 'test-url/test-assets/pps2__preview.jpg',
  155. source: 'test-url/test-assets/pps2.jpg',
  156. type: 'IMAGE',
  157. },
  158. ],
  159. );
  160. createdAssetId = createAssets[0].id;
  161. });
  162. it('permitted type by file extension', async () => {
  163. const filesToUpload = [path.join(__dirname, 'fixtures/assets/dummy.pdf')];
  164. const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
  165. mutation: CREATE_ASSETS,
  166. filePaths: filesToUpload,
  167. mapVariables: filePaths => ({
  168. input: filePaths.map(p => ({ file: null })),
  169. }),
  170. });
  171. expect(createAssets.map(a => omit(a, ['id']))).toEqual([
  172. {
  173. fileSize: 1680,
  174. focalPoint: null,
  175. mimeType: 'application/pdf',
  176. name: 'dummy.pdf',
  177. preview: 'test-url/test-assets/dummy__preview.pdf.png',
  178. source: 'test-url/test-assets/dummy.pdf',
  179. type: 'BINARY',
  180. },
  181. ]);
  182. });
  183. it(
  184. 'not permitted type',
  185. assertThrowsWithMessage(async () => {
  186. const filesToUpload = [path.join(__dirname, 'fixtures/assets/dummy.txt')];
  187. const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
  188. mutation: CREATE_ASSETS,
  189. filePaths: filesToUpload,
  190. mapVariables: filePaths => ({
  191. input: filePaths.map(p => ({ file: null })),
  192. }),
  193. });
  194. }, `The MIME type 'text/plain' is not permitted.`),
  195. );
  196. });
  197. describe('updateAsset', () => {
  198. it('update name', async () => {
  199. const { updateAsset } = await adminClient.query<UpdateAsset.Mutation, UpdateAsset.Variables>(
  200. UPDATE_ASSET,
  201. {
  202. input: {
  203. id: firstAssetId,
  204. name: 'new name',
  205. },
  206. },
  207. );
  208. expect(updateAsset.name).toEqual('new name');
  209. });
  210. it('update focalPoint', async () => {
  211. const { updateAsset } = await adminClient.query<UpdateAsset.Mutation, UpdateAsset.Variables>(
  212. UPDATE_ASSET,
  213. {
  214. input: {
  215. id: firstAssetId,
  216. focalPoint: {
  217. x: 0.3,
  218. y: 0.9,
  219. },
  220. },
  221. },
  222. );
  223. expect(updateAsset.focalPoint).toEqual({
  224. x: 0.3,
  225. y: 0.9,
  226. });
  227. });
  228. it('unset focalPoint', async () => {
  229. const { updateAsset } = await adminClient.query<UpdateAsset.Mutation, UpdateAsset.Variables>(
  230. UPDATE_ASSET,
  231. {
  232. input: {
  233. id: firstAssetId,
  234. focalPoint: null,
  235. },
  236. },
  237. );
  238. expect(updateAsset.focalPoint).toEqual(null);
  239. });
  240. });
  241. describe('deleteAsset', () => {
  242. let firstProduct: GetProductWithVariants.Product;
  243. beforeAll(async () => {
  244. const { product } = await adminClient.query<
  245. GetProductWithVariants.Query,
  246. GetProductWithVariants.Variables
  247. >(GET_PRODUCT_WITH_VARIANTS, {
  248. id: 'T_1',
  249. });
  250. firstProduct = product!;
  251. });
  252. it('non-featured asset', async () => {
  253. const { deleteAsset } = await adminClient.query<DeleteAsset.Mutation, DeleteAsset.Variables>(
  254. DELETE_ASSET,
  255. {
  256. id: createdAssetId,
  257. },
  258. );
  259. expect(deleteAsset.result).toBe(DeletionResult.DELETED);
  260. const { asset } = await adminClient.query<GetAsset.Query, GetAsset.Variables>(GET_ASSET, {
  261. id: createdAssetId,
  262. });
  263. expect(asset).toBeNull();
  264. });
  265. it('featured asset not deleted', async () => {
  266. const { deleteAsset } = await adminClient.query<DeleteAsset.Mutation, DeleteAsset.Variables>(
  267. DELETE_ASSET,
  268. {
  269. id: firstProduct.featuredAsset!.id,
  270. },
  271. );
  272. expect(deleteAsset.result).toBe(DeletionResult.NOT_DELETED);
  273. expect(deleteAsset.message).toContain(`The selected Asset is featured by 1 Product`);
  274. const { asset } = await adminClient.query<GetAsset.Query, GetAsset.Variables>(GET_ASSET, {
  275. id: firstAssetId,
  276. });
  277. expect(asset).not.toBeNull();
  278. });
  279. it('featured asset force deleted', async () => {
  280. const { product: p1 } = await adminClient.query<
  281. GetProductWithVariants.Query,
  282. GetProductWithVariants.Variables
  283. >(GET_PRODUCT_WITH_VARIANTS, {
  284. id: firstProduct.id,
  285. });
  286. expect(p1!.assets.length).toEqual(1);
  287. const { deleteAsset } = await adminClient.query<DeleteAsset.Mutation, DeleteAsset.Variables>(
  288. DELETE_ASSET,
  289. {
  290. id: firstProduct.featuredAsset!.id,
  291. force: true,
  292. },
  293. );
  294. expect(deleteAsset.result).toBe(DeletionResult.DELETED);
  295. const { asset } = await adminClient.query<GetAsset.Query, GetAsset.Variables>(GET_ASSET, {
  296. id: firstAssetId,
  297. });
  298. expect(asset).not.toBeNull();
  299. const { product } = await adminClient.query<
  300. GetProductWithVariants.Query,
  301. GetProductWithVariants.Variables
  302. >(GET_PRODUCT_WITH_VARIANTS, {
  303. id: firstProduct.id,
  304. });
  305. expect(product!.featuredAsset).toBeNull();
  306. expect(product!.assets.length).toEqual(0);
  307. });
  308. });
  309. });
  310. export const GET_ASSET = gql`
  311. query GetAsset($id: ID!) {
  312. asset(id: $id) {
  313. ...Asset
  314. width
  315. height
  316. }
  317. }
  318. ${ASSET_FRAGMENT}
  319. `;
  320. export const GET_ASSET_FRAGMENT_FIRST = gql`
  321. fragment AssetFragFirst on Asset {
  322. id
  323. preview
  324. }
  325. query GetAssetFragmentFirst($id: ID!) {
  326. asset(id: $id) {
  327. ...AssetFragFirst
  328. }
  329. }
  330. `;
  331. export const CREATE_ASSETS = gql`
  332. mutation CreateAssets($input: [CreateAssetInput!]!) {
  333. createAssets(input: $input) {
  334. ...Asset
  335. focalPoint {
  336. x
  337. y
  338. }
  339. }
  340. }
  341. ${ASSET_FRAGMENT}
  342. `;