asset.e2e-spec.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /* tslint:disable:no-non-null-assertion */
  2. import { omit } from '@vendure/common/lib/omit';
  3. import { mergeConfig } from '@vendure/core';
  4. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } 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. AssetFragment,
  12. CreateAssets,
  13. DeleteAsset,
  14. DeletionResult,
  15. GetAsset,
  16. GetAssetFragmentFirst,
  17. GetAssetList,
  18. GetProductWithVariants,
  19. SortOrder,
  20. UpdateAsset,
  21. } from './graphql/generated-e2e-admin-types';
  22. import {
  23. DELETE_ASSET,
  24. GET_ASSET_LIST,
  25. GET_PRODUCT_WITH_VARIANTS,
  26. UPDATE_ASSET,
  27. } from './graphql/shared-definitions';
  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. function isAsset(input: CreateAssets.CreateAssets): input is AssetFragment {
  127. return input.hasOwnProperty('name');
  128. }
  129. it('permitted types by mime type', async () => {
  130. const filesToUpload = [
  131. path.join(__dirname, 'fixtures/assets/pps1.jpg'),
  132. path.join(__dirname, 'fixtures/assets/pps2.jpg'),
  133. ];
  134. const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
  135. mutation: CREATE_ASSETS,
  136. filePaths: filesToUpload,
  137. mapVariables: filePaths => ({
  138. input: filePaths.map(p => ({ file: null })),
  139. }),
  140. });
  141. expect(createAssets.length).toBe(2);
  142. const results = createAssets.filter(isAsset);
  143. expect(results.map(a => omit(a, ['id'])).sort((a, b) => (a.name < b.name ? -1 : 1))).toEqual([
  144. {
  145. fileSize: 1680,
  146. focalPoint: null,
  147. mimeType: 'image/jpeg',
  148. name: 'pps1.jpg',
  149. preview: 'test-url/test-assets/pps1__preview.jpg',
  150. source: 'test-url/test-assets/pps1.jpg',
  151. type: 'IMAGE',
  152. },
  153. {
  154. fileSize: 1680,
  155. focalPoint: null,
  156. mimeType: 'image/jpeg',
  157. name: 'pps2.jpg',
  158. preview: 'test-url/test-assets/pps2__preview.jpg',
  159. source: 'test-url/test-assets/pps2.jpg',
  160. type: 'IMAGE',
  161. },
  162. ]);
  163. createdAssetId = results[0].id;
  164. });
  165. it('permitted type by file extension', async () => {
  166. const filesToUpload = [path.join(__dirname, 'fixtures/assets/dummy.pdf')];
  167. const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
  168. mutation: CREATE_ASSETS,
  169. filePaths: filesToUpload,
  170. mapVariables: filePaths => ({
  171. input: filePaths.map(p => ({ file: null })),
  172. }),
  173. });
  174. expect(createAssets.length).toBe(1);
  175. const results = createAssets.filter(isAsset);
  176. expect(results.map(a => omit(a, ['id']))).toEqual([
  177. {
  178. fileSize: 1680,
  179. focalPoint: null,
  180. mimeType: 'application/pdf',
  181. name: 'dummy.pdf',
  182. preview: 'test-url/test-assets/dummy__preview.pdf.png',
  183. source: 'test-url/test-assets/dummy.pdf',
  184. type: 'BINARY',
  185. },
  186. ]);
  187. });
  188. it('not permitted type', async () => {
  189. const filesToUpload = [path.join(__dirname, 'fixtures/assets/dummy.txt')];
  190. const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
  191. mutation: CREATE_ASSETS,
  192. filePaths: filesToUpload,
  193. mapVariables: filePaths => ({
  194. input: filePaths.map(p => ({ file: null })),
  195. }),
  196. });
  197. expect(createAssets.length).toBe(1);
  198. expect(createAssets[0]).toEqual({
  199. message: `The MIME type 'text/plain' is not permitted.`,
  200. mimeType: 'text/plain',
  201. fileName: 'dummy.txt',
  202. });
  203. });
  204. });
  205. describe('updateAsset', () => {
  206. it('update name', async () => {
  207. const { updateAsset } = await adminClient.query<UpdateAsset.Mutation, UpdateAsset.Variables>(
  208. UPDATE_ASSET,
  209. {
  210. input: {
  211. id: firstAssetId,
  212. name: 'new name',
  213. },
  214. },
  215. );
  216. expect(updateAsset.name).toEqual('new name');
  217. });
  218. it('update focalPoint', async () => {
  219. const { updateAsset } = await adminClient.query<UpdateAsset.Mutation, UpdateAsset.Variables>(
  220. UPDATE_ASSET,
  221. {
  222. input: {
  223. id: firstAssetId,
  224. focalPoint: {
  225. x: 0.3,
  226. y: 0.9,
  227. },
  228. },
  229. },
  230. );
  231. expect(updateAsset.focalPoint).toEqual({
  232. x: 0.3,
  233. y: 0.9,
  234. });
  235. });
  236. it('unset focalPoint', async () => {
  237. const { updateAsset } = await adminClient.query<UpdateAsset.Mutation, UpdateAsset.Variables>(
  238. UPDATE_ASSET,
  239. {
  240. input: {
  241. id: firstAssetId,
  242. focalPoint: null,
  243. },
  244. },
  245. );
  246. expect(updateAsset.focalPoint).toEqual(null);
  247. });
  248. });
  249. describe('deleteAsset', () => {
  250. let firstProduct: GetProductWithVariants.Product;
  251. beforeAll(async () => {
  252. const { product } = await adminClient.query<
  253. GetProductWithVariants.Query,
  254. GetProductWithVariants.Variables
  255. >(GET_PRODUCT_WITH_VARIANTS, {
  256. id: 'T_1',
  257. });
  258. firstProduct = product!;
  259. });
  260. it('non-featured asset', async () => {
  261. const { deleteAsset } = await adminClient.query<DeleteAsset.Mutation, DeleteAsset.Variables>(
  262. DELETE_ASSET,
  263. {
  264. id: createdAssetId,
  265. },
  266. );
  267. expect(deleteAsset.result).toBe(DeletionResult.DELETED);
  268. const { asset } = await adminClient.query<GetAsset.Query, GetAsset.Variables>(GET_ASSET, {
  269. id: createdAssetId,
  270. });
  271. expect(asset).toBeNull();
  272. });
  273. it('featured asset not deleted', async () => {
  274. const { deleteAsset } = await adminClient.query<DeleteAsset.Mutation, DeleteAsset.Variables>(
  275. DELETE_ASSET,
  276. {
  277. id: firstProduct.featuredAsset!.id,
  278. },
  279. );
  280. expect(deleteAsset.result).toBe(DeletionResult.NOT_DELETED);
  281. expect(deleteAsset.message).toContain(`The selected Asset is featured by 1 Product`);
  282. const { asset } = await adminClient.query<GetAsset.Query, GetAsset.Variables>(GET_ASSET, {
  283. id: firstAssetId,
  284. });
  285. expect(asset).not.toBeNull();
  286. });
  287. it('featured asset force deleted', async () => {
  288. const { product: p1 } = await adminClient.query<
  289. GetProductWithVariants.Query,
  290. GetProductWithVariants.Variables
  291. >(GET_PRODUCT_WITH_VARIANTS, {
  292. id: firstProduct.id,
  293. });
  294. expect(p1!.assets.length).toEqual(1);
  295. const { deleteAsset } = await adminClient.query<DeleteAsset.Mutation, DeleteAsset.Variables>(
  296. DELETE_ASSET,
  297. {
  298. id: firstProduct.featuredAsset!.id,
  299. force: true,
  300. },
  301. );
  302. expect(deleteAsset.result).toBe(DeletionResult.DELETED);
  303. const { asset } = await adminClient.query<GetAsset.Query, GetAsset.Variables>(GET_ASSET, {
  304. id: firstAssetId,
  305. });
  306. expect(asset).not.toBeNull();
  307. const { product } = await adminClient.query<
  308. GetProductWithVariants.Query,
  309. GetProductWithVariants.Variables
  310. >(GET_PRODUCT_WITH_VARIANTS, {
  311. id: firstProduct.id,
  312. });
  313. expect(product!.featuredAsset).toBeNull();
  314. expect(product!.assets.length).toEqual(0);
  315. });
  316. });
  317. });
  318. export const GET_ASSET = gql`
  319. query GetAsset($id: ID!) {
  320. asset(id: $id) {
  321. ...Asset
  322. width
  323. height
  324. }
  325. }
  326. ${ASSET_FRAGMENT}
  327. `;
  328. export const GET_ASSET_FRAGMENT_FIRST = gql`
  329. fragment AssetFragFirst on Asset {
  330. id
  331. preview
  332. }
  333. query GetAssetFragmentFirst($id: ID!) {
  334. asset(id: $id) {
  335. ...AssetFragFirst
  336. }
  337. }
  338. `;
  339. export const CREATE_ASSETS = gql`
  340. mutation CreateAssets($input: [CreateAssetInput!]!) {
  341. createAssets(input: $input) {
  342. ...Asset
  343. ... on Asset {
  344. focalPoint {
  345. x
  346. y
  347. }
  348. }
  349. ... on MimeTypeError {
  350. message
  351. fileName
  352. mimeType
  353. }
  354. }
  355. }
  356. ${ASSET_FRAGMENT}
  357. `;