@lenne.tech/nest-server 8.6.6 → 8.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/core/common/decorators/graphql-user.decorator.js +2 -3
  2. package/dist/core/common/decorators/graphql-user.decorator.js.map +1 -1
  3. package/dist/core/common/decorators/restricted.decorator.js +14 -13
  4. package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
  5. package/dist/core/common/helpers/context.helper.js +4 -5
  6. package/dist/core/common/helpers/context.helper.js.map +1 -1
  7. package/dist/core/common/helpers/db.helper.js +34 -22
  8. package/dist/core/common/helpers/db.helper.js.map +1 -1
  9. package/dist/core/common/helpers/file.helper.js +5 -1
  10. package/dist/core/common/helpers/file.helper.js.map +1 -1
  11. package/dist/core/common/helpers/filter.helper.js +16 -9
  12. package/dist/core/common/helpers/filter.helper.js.map +1 -1
  13. package/dist/core/common/helpers/graphql.helper.js +11 -8
  14. package/dist/core/common/helpers/graphql.helper.js.map +1 -1
  15. package/dist/core/common/helpers/input.helper.js +17 -11
  16. package/dist/core/common/helpers/input.helper.js.map +1 -1
  17. package/dist/core/common/helpers/model.helper.d.ts +2 -0
  18. package/dist/core/common/helpers/model.helper.js +125 -3
  19. package/dist/core/common/helpers/model.helper.js.map +1 -1
  20. package/dist/core/common/helpers/service.helper.d.ts +2 -0
  21. package/dist/core/common/helpers/service.helper.js +30 -16
  22. package/dist/core/common/helpers/service.helper.js.map +1 -1
  23. package/dist/core/common/inputs/core-input.input.js +6 -1
  24. package/dist/core/common/inputs/core-input.input.js.map +1 -1
  25. package/dist/core/common/inputs/single-filter.input.d.ts +1 -0
  26. package/dist/core/common/inputs/single-filter.input.js +8 -0
  27. package/dist/core/common/inputs/single-filter.input.js.map +1 -1
  28. package/dist/core/common/interfaces/prepare-input-options.interface.d.ts +1 -0
  29. package/dist/core/common/interfaces/prepare-output-options.interface.d.ts +1 -0
  30. package/dist/core/common/interfaces/service-options.interface.d.ts +3 -1
  31. package/dist/core/common/models/core-model.model.js +14 -2
  32. package/dist/core/common/models/core-model.model.js.map +1 -1
  33. package/dist/core/common/pipes/check-input.pipe.js +1 -1
  34. package/dist/core/common/pipes/check-input.pipe.js.map +1 -1
  35. package/dist/core/common/pipes/map-and-validate.pipe.js +2 -2
  36. package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -1
  37. package/dist/core/common/services/crud.service.js +3 -5
  38. package/dist/core/common/services/crud.service.js.map +1 -1
  39. package/dist/core/common/services/email.service.js +5 -1
  40. package/dist/core/common/services/email.service.js.map +1 -1
  41. package/dist/core/common/services/mailjet.service.d.ts +1 -1
  42. package/dist/core/common/services/mailjet.service.js +10 -3
  43. package/dist/core/common/services/mailjet.service.js.map +1 -1
  44. package/dist/core/common/services/module.service.js +41 -7
  45. package/dist/core/common/services/module.service.js.map +1 -1
  46. package/dist/core/common/types/field-selection.type.d.ts +1 -1
  47. package/dist/core/modules/auth/core-auth.module.js +2 -2
  48. package/dist/core/modules/auth/core-auth.module.js.map +1 -1
  49. package/dist/core/modules/auth/guards/auth.guard.js +4 -5
  50. package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
  51. package/dist/core/modules/auth/guards/roles.guard.js +1 -2
  52. package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
  53. package/dist/core/modules/file/core-file.service.d.ts +26 -0
  54. package/dist/core/modules/file/core-file.service.js +116 -0
  55. package/dist/core/modules/file/core-file.service.js.map +1 -0
  56. package/dist/core/modules/file/file-info.output.d.ts +10 -0
  57. package/dist/core/modules/file/file-info.output.js +49 -0
  58. package/dist/core/modules/file/file-info.output.js.map +1 -0
  59. package/dist/core/modules/file/interfaces/file-service-options.interface.d.ts +7 -0
  60. package/dist/core/modules/file/interfaces/file-service-options.interface.js +3 -0
  61. package/dist/core/modules/file/interfaces/file-service-options.interface.js.map +1 -0
  62. package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +13 -0
  63. package/dist/core/modules/file/interfaces/file-upload.interface.js +3 -0
  64. package/dist/core/modules/file/interfaces/file-upload.interface.js.map +1 -0
  65. package/dist/core/modules/user/core-user.service.js +7 -3
  66. package/dist/core/modules/user/core-user.service.js.map +1 -1
  67. package/dist/core.module.js +34 -39
  68. package/dist/core.module.js.map +1 -1
  69. package/dist/server/modules/auth/auth.module.js +4 -1
  70. package/dist/server/modules/auth/auth.module.js.map +1 -1
  71. package/dist/server/modules/file/file.controller.d.ts +6 -1
  72. package/dist/server/modules/file/file.controller.js +33 -3
  73. package/dist/server/modules/file/file.controller.js.map +1 -1
  74. package/dist/server/modules/file/file.resolver.d.ts +8 -2
  75. package/dist/server/modules/file/file.resolver.js +47 -11
  76. package/dist/server/modules/file/file.resolver.js.map +1 -1
  77. package/dist/server/modules/file/file.service.d.ts +6 -0
  78. package/dist/server/modules/file/file.service.js +32 -0
  79. package/dist/server/modules/file/file.service.js.map +1 -0
  80. package/dist/server/modules/user/user.model.d.ts +15 -1
  81. package/dist/server/modules/user/user.model.js +5 -0
  82. package/dist/server/modules/user/user.model.js.map +1 -1
  83. package/dist/server/modules/user/user.resolver.js +1 -2
  84. package/dist/server/modules/user/user.resolver.js.map +1 -1
  85. package/dist/server/modules/user/user.service.js +2 -2
  86. package/dist/server/modules/user/user.service.js.map +1 -1
  87. package/dist/server/server.module.js +2 -1
  88. package/dist/server/server.module.js.map +1 -1
  89. package/dist/test/test.helper.d.ts +21 -1
  90. package/dist/test/test.helper.js +99 -12
  91. package/dist/test/test.helper.js.map +1 -1
  92. package/dist/tsconfig.build.tsbuildinfo +1 -1
  93. package/package.json +31 -29
  94. package/src/core/common/helpers/db.helper.ts +9 -2
  95. package/src/core/common/helpers/filter.helper.ts +7 -1
  96. package/src/core/common/helpers/model.helper.ts +152 -0
  97. package/src/core/common/helpers/service.helper.ts +6 -3
  98. package/src/core/common/inputs/single-filter.input.ts +10 -1
  99. package/src/core/common/interfaces/prepare-input-options.interface.ts +1 -0
  100. package/src/core/common/interfaces/prepare-output-options.interface.ts +1 -0
  101. package/src/core/common/interfaces/service-options.interface.ts +8 -2
  102. package/src/core/common/services/mailjet.service.ts +1 -1
  103. package/src/core/common/services/module.service.ts +18 -1
  104. package/src/core/common/types/field-selection.type.ts +1 -1
  105. package/src/core/modules/file/core-file.service.ts +179 -0
  106. package/src/core/modules/file/file-info.output.ts +26 -0
  107. package/src/core/modules/file/interfaces/file-service-options.interface.ts +7 -0
  108. package/src/core/modules/file/interfaces/file-upload.interface.ts +38 -0
  109. package/src/core.module.ts +1 -1
  110. package/src/server/modules/file/file.controller.ts +42 -3
  111. package/src/server/modules/file/file.resolver.ts +58 -33
  112. package/src/server/modules/file/file.service.ts +11 -0
  113. package/src/server/modules/user/user.model.ts +9 -0
  114. package/src/server/server.module.ts +2 -1
  115. package/src/test/test.helper.ts +122 -6
@@ -0,0 +1,26 @@
1
+ import { Field, ObjectType } from '@nestjs/graphql';
2
+ import { Types } from 'mongoose';
3
+ import { CoreModel } from '../../common/models/core-model.model';
4
+
5
+ /**
6
+ * File info (output)
7
+ */
8
+ @ObjectType({ description: 'Information about attachment file' })
9
+ export class FileInfo extends CoreModel {
10
+ _id: Types.ObjectId;
11
+
12
+ @Field(() => String, { description: 'ID of the file in bytes' })
13
+ id: string = undefined;
14
+
15
+ @Field(() => Number, { description: 'Length of the file in bytes', nullable: true })
16
+ length: number = undefined;
17
+
18
+ @Field(() => Number, { description: 'Size of the chunk', nullable: true })
19
+ chunkSize: number = undefined;
20
+
21
+ @Field(() => String, { description: 'Name of the file', nullable: true })
22
+ filename?: string = undefined;
23
+
24
+ @Field(() => String, { description: 'Content type', nullable: true })
25
+ contentType?: string = undefined;
26
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Interface for service options in file services
3
+ */
4
+ export interface FileServiceOptions {
5
+ currentUser?: { id: any; hasRole: (roles: string[]) => boolean };
6
+ roles?: string | string[];
7
+ }
@@ -0,0 +1,38 @@
1
+ import { WriteStream } from 'fs-capacitor';
2
+ import { Readable } from 'stream';
3
+
4
+ /**
5
+ * Interface for file uploads
6
+ */
7
+ export interface FileUpload {
8
+ /**
9
+ * A private implementation detail that shouldn’t be used outside
10
+ */
11
+ capacitor: WriteStream;
12
+
13
+ /**
14
+ * A function that returns a FileUploadCreateReadStream.
15
+ */
16
+ createReadStream: (options?: {
17
+ /** Specify an encoding for the chunks, default: utf8 */
18
+ encoding?: 'utf8' | 'utf8' | 'ucs2' | 'utf16le' | 'latin1' | 'ascii' | 'base64' | 'base64url' | 'hex';
19
+
20
+ /** Maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource, default: 16384 */
21
+ highWaterMark?: number;
22
+ }) => Readable;
23
+
24
+ /**
25
+ * Stream transfer encoding of the file
26
+ */
27
+ encoding: string;
28
+
29
+ /**
30
+ * Name of the file
31
+ */
32
+ filename: string;
33
+
34
+ /**
35
+ * Mimetype of the file
36
+ */
37
+ mimetype: string;
38
+ }
@@ -4,7 +4,7 @@ import { APP_PIPE } from '@nestjs/core';
4
4
  import { GraphQLModule } from '@nestjs/graphql';
5
5
  import { MongooseModule } from '@nestjs/mongoose';
6
6
  import { Context } from 'apollo-server-core';
7
- import { graphqlUploadExpress } from 'graphql-upload';
7
+ import * as graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.js';
8
8
  import { merge } from './core/common/helpers/config.helper';
9
9
  import { IServerOptions } from './core/common/interfaces/server-options.interface';
10
10
  import { MapAndValidatePipe } from './core/common/pipes/map-and-validate.pipe';
@@ -1,10 +1,24 @@
1
- import { Body, Controller, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
1
+ import {
2
+ BadRequestException,
3
+ Body,
4
+ Controller,
5
+ Get,
6
+ NotFoundException,
7
+ Param,
8
+ Post,
9
+ Res,
10
+ UploadedFiles,
11
+ UseInterceptors,
12
+ } from '@nestjs/common';
2
13
  import { FilesInterceptor } from '@nestjs/platform-express';
3
14
  import { diskStorage } from 'multer';
4
15
  import envConfig from '../../../config.env';
16
+ import { RESTUser } from '../../../core/common/decorators/rest-user.decorator';
5
17
  import { Roles } from '../../../core/common/decorators/roles.decorator';
6
18
  import { RoleEnum } from '../../../core/common/enums/role.enum';
7
19
  import { multerRandomFileName } from '../../../core/common/helpers/file.helper';
20
+ import { User } from '../user/user.model';
21
+ import { FileService } from './file.service';
8
22
 
9
23
  /**
10
24
  * File controller for
@@ -12,7 +26,12 @@ import { multerRandomFileName } from '../../../core/common/helpers/file.helper';
12
26
  @Controller('files')
13
27
  export class FileController {
14
28
  /**
15
- * Upload files
29
+ * Include services
30
+ */
31
+ constructor(protected fileService: FileService) {}
32
+
33
+ /**
34
+ * Upload files via REST as an alternative to uploading via GraphQL (see file.resolver.ts)
16
35
  */
17
36
  @Roles(RoleEnum.ADMIN)
18
37
  @Post('upload')
@@ -31,7 +50,27 @@ export class FileController {
31
50
  }),
32
51
  })
33
52
  )
34
- uploadFile(@UploadedFiles() files, @Body() fields: any) {
53
+ uploadFiles(@UploadedFiles() files, @Body() fields: any) {
35
54
  console.log(JSON.stringify({ files, fields }, null, 2));
36
55
  }
56
+
57
+ /**
58
+ * Download file
59
+ */
60
+ @Roles(RoleEnum.ADMIN)
61
+ @Get(':filename')
62
+ async getFile(@Param('filename') filename: string, @Res() res, @RESTUser() user: User) {
63
+ if (!filename) {
64
+ throw new BadRequestException('Missing filename for download');
65
+ }
66
+
67
+ const file = await this.fileService.getFileInfoByName(filename);
68
+ if (!file) {
69
+ throw new NotFoundException('File not found');
70
+ }
71
+ const filestream = await this.fileService.getFileStream(file.id);
72
+ res.header('Content-Type', file.contentType);
73
+ res.header('Content-Disposition', 'attachment; filename=' + file.filename);
74
+ return filestream.pipe(res);
75
+ }
37
76
  }
@@ -1,55 +1,80 @@
1
- import { Args, Mutation, Resolver } from '@nestjs/graphql';
1
+ import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
2
2
  import { createWriteStream } from 'fs';
3
- import { FileUpload, GraphQLUpload } from 'graphql-upload';
3
+ import * as GraphQLUpload from 'graphql-upload/GraphQLUpload.js';
4
+ import { Roles } from '../../../core/common/decorators/roles.decorator';
5
+ import { RoleEnum } from '../../../core/common/enums/role.enum';
6
+ import { FileInfo } from '../../../core/modules/file/file-info.output';
7
+ import { FileUpload } from '../../../core/modules/file/interfaces/file-upload.interface';
8
+ import { FileService } from './file.service';
4
9
 
5
10
  /**
6
11
  * File resolver
7
12
  */
8
13
  @Resolver()
9
14
  export class FileResolver {
15
+ /**
16
+ * Integrate services
17
+ */
18
+ constructor(protected readonly fileService: FileService) {}
19
+
20
+ // ===========================================================================
21
+ // Queries
22
+ // ===========================================================================
23
+
24
+ /**
25
+ * Get file info
26
+ */
27
+ @Roles(RoleEnum.ADMIN)
28
+ @Query(() => FileInfo, { nullable: true })
29
+ async getFileInfo(@Args({ name: 'filename', type: () => String }) filename: string) {
30
+ return await this.fileService.getFileInfoByName(filename);
31
+ }
32
+
33
+ // ===========================================================================
34
+ // Mutations
35
+ // ===========================================================================
36
+
37
+ /**
38
+ * Delete file
39
+ */
40
+ @Roles(RoleEnum.ADMIN)
41
+ @Mutation(() => FileInfo)
42
+ async deleteFile(@Args({ name: 'filename', type: () => String }) filename: string) {
43
+ return await this.fileService.deleteFileByName(filename);
44
+ }
45
+
10
46
  /**
11
47
  * Upload file
12
48
  */
13
- @Mutation(() => Boolean)
14
- async uploadFile(
15
- @Args({ name: 'file', type: () => GraphQLUpload })
16
- file: FileUpload
17
- ) {
18
- console.log(JSON.stringify(file, null, 2));
19
- /*
20
- const {filename, mimetype, encoding, createReadStream} = file;
21
- await new Promise((resolve, reject) =>
22
- createReadStream()
23
- .pipe(createWriteStream(`./uploads/${filename}`))
24
- .on('finish', () => resolve(true))
25
- .on('error', (error) => reject(error))
26
- );
27
- */
28
- return true;
49
+ @Roles(RoleEnum.ADMIN)
50
+ @Mutation(() => FileInfo)
51
+ async uploadFile(@Args({ name: 'file', type: () => GraphQLUpload }) file: FileUpload) {
52
+ const { filename, mimetype, encoding, createReadStream } = file;
53
+
54
+ // Save file in DB
55
+ return await this.fileService.createFile(file);
29
56
  }
30
57
 
31
58
  /**
32
59
  * Upload files
33
60
  */
61
+ @Roles(RoleEnum.ADMIN)
34
62
  @Mutation(() => Boolean)
35
- async uploadFiles(
36
- @Args({ name: 'files', type: () => [GraphQLUpload] })
37
- files: FileUpload[]
38
- ) {
63
+ async uploadFiles(@Args({ name: 'files', type: () => [GraphQLUpload] }) files: FileUpload[]) {
64
+ // Save files in filesystem
39
65
  const promises: Promise<any>[] = [];
40
66
  for (const file of files) {
41
- console.log(JSON.stringify(await file, null, 2));
42
- /*
43
- const {filename, mimetype, encoding, createReadStream} = await file
44
- promises.push(new Promise((resolve, reject) =>
45
- createReadStream()
46
- .pipe(createWriteStream(`./uploads/${filename}`))
47
- .on('finish', () => resolve(true))
48
- .on('error', (error) => reject(error))
49
- ));
50
- */
67
+ const { filename, mimetype, encoding, createReadStream } = await file;
68
+ promises.push(
69
+ new Promise((resolve, reject) =>
70
+ createReadStream()
71
+ .pipe(createWriteStream(`./uploads/${filename}`))
72
+ .on('finish', () => resolve(true))
73
+ .on('error', (error) => reject(error))
74
+ )
75
+ );
51
76
  }
52
- await Promise.all(promises);
77
+ await Promise.allSettled(promises);
53
78
  return true;
54
79
  }
55
80
  }
@@ -0,0 +1,11 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { InjectConnection } from '@nestjs/mongoose';
3
+ import { Connection } from 'mongoose';
4
+ import { CoreFileService } from '../../../core/modules/file/core-file.service';
5
+
6
+ @Injectable()
7
+ export class FileService extends CoreFileService {
8
+ constructor(@InjectConnection() protected readonly connection: Connection) {
9
+ super(connection, 'File');
10
+ }
11
+ }
@@ -1,6 +1,7 @@
1
1
  import { Field, ObjectType } from '@nestjs/graphql';
2
2
  import { Prop, Schema as MongooseSchema, SchemaFactory } from '@nestjs/mongoose';
3
3
  import { Document, Schema } from 'mongoose';
4
+ import { mapClasses } from '../../../core/common/helpers/model.helper';
4
5
  import { CoreUserModel } from '../../../core/modules/user/core-user.model';
5
6
  import { PersistenceModel } from '../../common/models/persistence.model';
6
7
 
@@ -59,6 +60,14 @@ export class User extends CoreUserModel implements PersistenceModel {
59
60
  // Nothing more to initialize yet
60
61
  return this;
61
62
  }
63
+
64
+ /**
65
+ * Map input
66
+ */
67
+ map(input) {
68
+ super.map(input);
69
+ return mapClasses(input, { createdBy: User, updatedBy: User }, this);
70
+ }
62
71
  }
63
72
 
64
73
  export const UserSchema = SchemaFactory.createForClass(User);
@@ -5,6 +5,7 @@ import { CoreAuthService } from '../core/modules/auth/services/core-auth.service
5
5
  import { AuthModule } from './modules/auth/auth.module';
6
6
  import { FileController } from './modules/file/file.controller';
7
7
  import { FileResolver } from './modules/file/file.resolver';
8
+ import { FileService } from './modules/file/file.service';
8
9
  import { ServerController } from './server.controller';
9
10
 
10
11
  /**
@@ -28,7 +29,7 @@ import { ServerController } from './server.controller';
28
29
  controllers: [FileController, ServerController],
29
30
 
30
31
  // Include resolvers, services and other providers
31
- providers: [FileResolver],
32
+ providers: [FileService, FileResolver],
32
33
 
33
34
  // Export modules for reuse in other modules
34
35
  exports: [CoreModule, AuthModule],
@@ -1,4 +1,6 @@
1
1
  import { INestApplication } from '@nestjs/common';
2
+ import { Blob } from 'buffer';
3
+ import * as fs from 'fs';
2
4
  import { createClient } from 'graphql-ws';
3
5
  import { jsonToGraphQLQuery } from 'json-to-graphql-query';
4
6
  import * as LightMyRequest from 'light-my-request';
@@ -53,6 +55,21 @@ export interface TestGraphQLConfig {
53
55
  * https://graphql.org/learn/queries
54
56
  */
55
57
  type?: TestGraphQLType;
58
+
59
+ /**
60
+ * GraphQL variables with variable name as key and Type as value
61
+ */
62
+ variables?: Record<string, string>;
63
+ }
64
+
65
+ export interface TestGraphQLAttachment {
66
+ file: Blob | Buffer | fs.ReadStream | string | boolean | number;
67
+ options?: string | { filename?: string | undefined; contentType?: string | undefined };
68
+ }
69
+
70
+ export interface TestGraphQLVariable {
71
+ value: string | string[] | TestGraphQLAttachment | TestGraphQLAttachment[] | any;
72
+ type: 'field' | 'attachment';
56
73
  }
57
74
 
58
75
  /**
@@ -89,6 +106,11 @@ export interface TestGraphQLOptions {
89
106
  * Token of user who is logged in
90
107
  */
91
108
  token?: string;
109
+
110
+ /**
111
+ * GraphQL variables
112
+ */
113
+ variables?: Record<string, TestGraphQLVariable>;
92
114
  }
93
115
 
94
116
  /**
@@ -122,6 +144,35 @@ export class TestHelper {
122
144
  this.subscriptionUrl = subscriptionUrl;
123
145
  }
124
146
 
147
+ /**
148
+ * Download file from URL
149
+ */
150
+ download(url: string, token?: string) {
151
+ return new Promise((resolve, reject) => {
152
+ const request = supertest((this.app as INestApplication).getHttpServer()).get(url);
153
+ if (token) {
154
+ request.set('Authorization', 'bearer ' + token);
155
+ }
156
+ let data = '';
157
+ request
158
+ .buffer()
159
+ .parse((res: any, callback) => {
160
+ res.setEncoding('binary');
161
+ res.on('data', (chunk) => {
162
+ data += chunk;
163
+ });
164
+ res.on('error', reject);
165
+ res.on('end', (err) => {
166
+ err ? reject(err) : callback(null, null);
167
+ });
168
+ })
169
+ .end((err, res: unknown) => {
170
+ (res as Response & { data: string }).data = new Buffer(data, 'binary').toString();
171
+ err ? reject(err) : resolve(res);
172
+ });
173
+ });
174
+ }
175
+
125
176
  /**
126
177
  * GraphQL request
127
178
  */
@@ -138,7 +189,7 @@ export class TestHelper {
138
189
  };
139
190
 
140
191
  // Init vars
141
- const { token, statusCode, log, logError } = config;
192
+ const { token, statusCode, log, logError, variables } = config;
142
193
 
143
194
  // Init
144
195
  let query = '';
@@ -170,6 +221,11 @@ export class TestHelper {
170
221
  // Set request type
171
222
  queryObj[graphql.type] = {};
172
223
 
224
+ // Set variables
225
+ if (graphql.variables) {
226
+ queryObj[graphql.type]['__variables'] = graphql.variables;
227
+ }
228
+
173
229
  // Set request name and fields
174
230
  queryObj[graphql.type][graphql.name] = this.prepareFields(graphql.fields) || {};
175
231
 
@@ -212,7 +268,7 @@ export class TestHelper {
212
268
  }
213
269
 
214
270
  // Get response
215
- const response = await this.getResponse(token, requestConfig, statusCode, log, logError);
271
+ const response = await this.getResponse(token, requestConfig, statusCode, log, logError, variables);
216
272
 
217
273
  // Response of fastify
218
274
  // if ((this.app as FastifyInstance).inject) {
@@ -325,7 +381,8 @@ export class TestHelper {
325
381
  requestConfig: LightMyRequest.InjectOptions,
326
382
  statusCode: number,
327
383
  log: boolean,
328
- logError: boolean
384
+ logError: boolean,
385
+ variables?: Record<string, TestGraphQLVariable>
329
386
  ): Promise<any> {
330
387
  // Token
331
388
  if (token) {
@@ -356,12 +413,71 @@ export class TestHelper {
356
413
  const method: string = requestConfig.method.toLowerCase();
357
414
  let request = supertest((this.app as INestApplication).getHttpServer())[method](requestConfig.url as string);
358
415
  if (token) {
359
- request = request.set('Authorization', 'bearer ' + token);
416
+ request.set('Authorization', 'bearer ' + token);
417
+ }
418
+
419
+ // Process variables
420
+ if (variables) {
421
+ request = this.processVariables(request, variables, (requestConfig.payload as any)?.query);
360
422
  }
361
423
 
362
424
  // Response
363
- const response = await request.send(requestConfig.payload);
425
+ const response = await (variables ? request : request.send(requestConfig.payload));
426
+ return this.processResponse(response, statusCode, log, logError);
427
+ }
428
+
429
+ /**
430
+ * Process GraphQL variables
431
+ */
432
+ processVariables(request: any, variables: Record<string, TestGraphQLVariable>, query: string | object) {
433
+ // Check and optimize parameters
434
+ if (!variables) {
435
+ return request;
436
+ }
437
+ if (typeof query === 'object') {
438
+ query = JSON.stringify(query).replace(/"/g, '');
439
+ }
440
+
441
+ // Create map
442
+ const mapArray: { key: string; type: 'attachment' | 'field'; value: any; index?: number }[] = [];
443
+ for (const [key, item] of Object.entries(variables)) {
444
+ if (item.type === 'attachment' && Array.isArray(item.value)) {
445
+ item.value.forEach((element, index) => {
446
+ mapArray.push({ key, type: 'attachment', value: element, index });
447
+ });
448
+ } else {
449
+ mapArray.push({ key, type: item.type, value: item.value });
450
+ }
451
+ }
452
+ const map = {};
453
+ mapArray.forEach((item, index) => {
454
+ map[index] = ['variables.' + item.key + ('index' in item ? '.' + item.index : '')];
455
+ });
364
456
 
457
+ // Add operations
458
+ request.field('operations', JSON.stringify({ query })).field('map', JSON.stringify(map));
459
+
460
+ // Add variables as attachment or field
461
+ mapArray.forEach((variable, i) => {
462
+ if (variable.type === 'attachment') {
463
+ if (typeof variable.value === 'object' && variable.value.file) {
464
+ request.attach(`${i}`, variable.value.file, variable.value.options);
465
+ } else {
466
+ request.attach(`${i}`, variable.value);
467
+ }
468
+ } else {
469
+ request.field(`${i}`, variable.value);
470
+ }
471
+ });
472
+
473
+ // Return processed request
474
+ return request;
475
+ }
476
+
477
+ /**
478
+ * Process GraphQL response
479
+ */
480
+ processResponse(response, statusCode, log, logError) {
365
481
  // Log response
366
482
  if (log) {
367
483
  console.log(JSON.stringify(response, null, 2));
@@ -372,7 +488,7 @@ export class TestHelper {
372
488
  if (response && response.error && response.error.text) {
373
489
  const errors = JSON.parse(response.error.text).errors;
374
490
  for (const error of errors) {
375
- console.log(util.inspect(error, false, null, true));
491
+ console.error(util.inspect(error, false, null, true));
376
492
  }
377
493
  }
378
494
  }