@anthonylzq/simba.js 5.2.0 → 6.2.0

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.
@@ -0,0 +1,615 @@
1
+ const { platform } = require('os')
2
+ const { promisify } = require('util')
3
+ const exec = promisify(require('child_process').exec)
4
+ const writeFile = require('../utils/writeFile')
5
+ const { ENVIRONMENTS_WITH_MONGO_URI } = require('../utils/constants')
6
+
7
+ /**
8
+ * @param {String} projectName
9
+ * @param {Boolean} graphql
10
+ * @returns {Promise<void>}
11
+ */
12
+ module.exports = async (projectName, graphql) => {
13
+ const createFoldersCommand = `mkdir ${projectName}/test`
14
+
15
+ if (platform() === 'win32')
16
+ await exec(createFoldersCommand.replaceAll('/', '\\'))
17
+ else await exec(createFoldersCommand)
18
+
19
+ const data = {
20
+ jestConfig: {
21
+ content: `import { Config } from '@jest/types'
22
+
23
+ const config: Config.InitialOptions = {
24
+ verbose: true,
25
+ preset: 'ts-jest',
26
+ testEnvironment: 'node',
27
+ testTimeout: 1 * 60 * 1000,
28
+ globalSetup: './test/jestGlobalSetup.ts',
29
+ modulePaths: ['<rootDir>/src', '<rootDir>/node_modules'],
30
+ roots: ['.'],
31
+ moduleFileExtensions: ['js', 'json', 'ts'],
32
+ testRegex: '.test.ts$',
33
+ transform: {
34
+ '^.+\\\\.(t|j)s$': 'ts-jest'
35
+ }
36
+ }
37
+
38
+ export default config
39
+ `,
40
+ file: `${projectName}/jest.config.ts`
41
+ },
42
+ test: {
43
+ index: {
44
+ content: graphql
45
+ ? `import axios from 'axios'
46
+ import { Static, TObject, TProperties, Type } from '@sinclair/typebox'
47
+ import Ajv from 'ajv'
48
+ import addFormats from 'ajv-formats'
49
+
50
+ import { Server } from '../src/network'
51
+ import { userDto } from '../src/schemas'
52
+
53
+ const ajv = addFormats(new Ajv(), ['email'])
54
+ .addKeyword('kind')
55
+ .addKeyword('modifier')
56
+
57
+ const BASE_URL = 'http://localhost:1996'
58
+ const validator = <T extends TProperties>(
59
+ schema: TObject<T>,
60
+ object: unknown
61
+ ) => {
62
+ const validate = ajv.compile(schema)
63
+
64
+ return validate(object)
65
+ }
66
+ const baseResponseDto = Type.Object({
67
+ error: Type.Boolean(),
68
+ message: Type.String()
69
+ })
70
+
71
+ type BaseResponseDTO = Static<typeof baseResponseDto>
72
+
73
+ describe('Simba.js tests', () => {
74
+ beforeAll(async () => {
75
+ await Server.start()
76
+ })
77
+
78
+ describe('API endpoints tests', () => {
79
+ let userID = ''
80
+
81
+ describe('API: GET /', () => {
82
+ let data: BaseResponseDTO
83
+
84
+ test('Should return 200 as status code', async () => {
85
+ const result = await axios.get<BaseResponseDTO>(BASE_URL)
86
+
87
+ data = result.data
88
+ expect(result.status).toBe(200)
89
+ })
90
+
91
+ test('Should be a successfully operation', () => {
92
+ expect(data.error).toBe(false)
93
+ })
94
+
95
+ test('Should be return baseResponseDto', () => {
96
+ expect(validator(baseResponseDto, data)).toBe(true)
97
+ })
98
+ })
99
+
100
+ describe('API: storeUser mutation', () => {
101
+ const storeUserResponse = Type.Object({
102
+ data: Type.Object({
103
+ user: userDto
104
+ })
105
+ })
106
+
107
+ type StoreUserDTO = Static<typeof storeUserResponse>
108
+
109
+ let data: StoreUserDTO
110
+ let status: number
111
+
112
+ test('Should return 200 as status code', async () => {
113
+ const result = await axios.post<StoreUserDTO>(\`\${BASE_URL}/api\`, {
114
+ query: \`mutation storeUser($user: StoreUserInput!) {
115
+ user: storeUser(user: $user) {
116
+ id
117
+ name
118
+ lastName
119
+ createdAt
120
+ updatedAt
121
+ }
122
+ }\`,
123
+ variables: {
124
+ user: {
125
+ lastName: 'Lzq',
126
+ name: 'Anthony'
127
+ }
128
+ }
129
+ })
130
+
131
+ data = result.data
132
+ status = result.status
133
+ userID = result.data.data.user.id ?? ''
134
+ expect(status).toBe(200)
135
+ })
136
+
137
+ test('Should return storeUserResponse', () => {
138
+ expect(validator(storeUserResponse, data)).toBe(true)
139
+ })
140
+ })
141
+
142
+ describe('API: getUsers query', () => {
143
+ const getUsersResponse = Type.Object({
144
+ data: Type.Object({
145
+ users: Type.Array(userDto)
146
+ })
147
+ })
148
+
149
+ type GetAllUsersDTO = Static<typeof getUsersResponse>
150
+
151
+ let data: GetAllUsersDTO
152
+ let status: number
153
+
154
+ test('Should return 200 as status code', async () => {
155
+ const result = await axios.post<GetAllUsersDTO>(\`\${BASE_URL}/api\`, {
156
+ query: \`query getUsers {
157
+ users: getUsers {
158
+ id
159
+ name
160
+ lastName
161
+ createdAt
162
+ updatedAt
163
+ }
164
+ }\`
165
+ })
166
+
167
+ data = result.data
168
+ status = result.status
169
+ expect(status).toBe(200)
170
+ })
171
+
172
+ test('Should return getUsersResponse', () => {
173
+ expect(validator(getUsersResponse, data)).toBe(true)
174
+ })
175
+ })
176
+
177
+ describe('API: getUser query', () => {
178
+ const getUserResponse = Type.Object({
179
+ data: Type.Object({
180
+ user: userDto
181
+ })
182
+ })
183
+
184
+ type GetOneUserDTO = Static<typeof getUserResponse>
185
+
186
+ let data: GetOneUserDTO
187
+ let status: number
188
+
189
+ test('Should return 200 as status code', async () => {
190
+ const result = await axios.post<GetOneUserDTO>(\`\${BASE_URL}/api\`, {
191
+ query: \`query getUser($id: ID!) {
192
+ user: getUser(id: $id) {
193
+ id
194
+ name
195
+ lastName
196
+ createdAt
197
+ updatedAt
198
+ }
199
+ }\`,
200
+ variables: {
201
+ id: userID
202
+ }
203
+ })
204
+
205
+ data = result.data
206
+ status = result.status
207
+ expect(status).toBe(200)
208
+ })
209
+
210
+ test('Should return getOneUserResponse', () => {
211
+ expect(validator(getUserResponse, data)).toBe(true)
212
+ })
213
+ })
214
+
215
+ describe('API: updateUser mutation', () => {
216
+ const updateUserResponse = Type.Object({
217
+ data: Type.Object({
218
+ user: userDto
219
+ })
220
+ })
221
+
222
+ type UpdateUserDTO = Static<typeof updateUserResponse>
223
+
224
+ let data: UpdateUserDTO
225
+ let status: number
226
+
227
+ test('Should return 200 as status code', async () => {
228
+ const result = await axios.post<UpdateUserDTO>(\`\${BASE_URL}/api\`, {
229
+ query: \`mutation updateUser($user: UpdateUserInput!) {
230
+ user: updateUser(user: $user) {
231
+ id
232
+ name
233
+ lastName
234
+ createdAt
235
+ updatedAt
236
+ }
237
+ }\`,
238
+ variables: {
239
+ user: {
240
+ id: userID,
241
+ lastName: 'Luzquiños',
242
+ name: 'Anthony'
243
+ }
244
+ }
245
+ })
246
+
247
+ data = result.data
248
+ status = result.status
249
+ expect(status).toBe(200)
250
+ })
251
+
252
+ test('Should return updateUserResponse', () => {
253
+ expect(validator(updateUserResponse, data)).toBe(true)
254
+ })
255
+ })
256
+
257
+ describe('API: deleteUser mutation', () => {
258
+ const deleteUserResponse = Type.Object({
259
+ data: Type.Object({
260
+ result: Type.String()
261
+ })
262
+ })
263
+
264
+ type DeleteUserDTO = Static<typeof deleteUserResponse>
265
+
266
+ let data: DeleteUserDTO
267
+ let status: number
268
+
269
+ test('Should return 200 as status code', async () => {
270
+ const result = await axios.post<DeleteUserDTO>(\`\${BASE_URL}/api\`, {
271
+ query: \`mutation deleteUser($id: ID!) {
272
+ result: deleteUser(id: $id)
273
+ }\`,
274
+ variables: {
275
+ id: userID
276
+ }
277
+ })
278
+
279
+ data = result.data
280
+ status = result.status
281
+ expect(status).toBe(200)
282
+ })
283
+
284
+ test('Should return deleteUserResponse', () => {
285
+ expect(validator(deleteUserResponse, data)).toBe(true)
286
+ })
287
+ })
288
+
289
+ describe('API: deleteAllUsers mutation', () => {
290
+ const deleteAllUserResponse = Type.Object({
291
+ data: Type.Object({
292
+ result: Type.String()
293
+ })
294
+ })
295
+
296
+ type DeleteAllUsersDTO = Static<typeof deleteAllUserResponse>
297
+
298
+ let data: DeleteAllUsersDTO
299
+ let status: number
300
+
301
+ test('Should return 200 as status code', async () => {
302
+ await axios.post(\`\${BASE_URL}/api\`, {
303
+ query: \`mutation storeUser($user: StoreUserInput!) {
304
+ user: storeUser(user: $user) {
305
+ id
306
+ name
307
+ lastName
308
+ createdAt
309
+ updatedAt
310
+ }
311
+ }\`,
312
+ variables: {
313
+ user: {
314
+ lastName: 'Lzq',
315
+ name: 'Anthony'
316
+ }
317
+ }
318
+ })
319
+
320
+ const result = await axios.post<DeleteAllUsersDTO>(\`\${BASE_URL}/api\`, {
321
+ query: \`mutation deleteAllUsers {
322
+ result: deleteAllUsers
323
+ }\`
324
+ })
325
+
326
+ data = result.data
327
+ status = result.status
328
+ expect(status).toBe(200)
329
+ })
330
+
331
+ test('Should return deleteAllUsersResponse', () => {
332
+ expect(validator(deleteAllUserResponse, data)).toBe(true)
333
+ })
334
+ })
335
+ })
336
+
337
+ afterAll(async () => {
338
+ await Server.stop()
339
+ })
340
+ })
341
+ `
342
+ : `import axios from 'axios'
343
+ import { Static, TObject, TProperties, Type } from '@sinclair/typebox'
344
+ import Ajv from 'ajv'
345
+
346
+ import { Server } from '../src/network'
347
+ import { userDto } from '../src/schemas'
348
+
349
+ const ajv = new Ajv({
350
+ removeAdditional: true,
351
+ useDefaults: true,
352
+ coerceTypes: true,
353
+ nullable: true
354
+ })
355
+
356
+ const BASE_URL = 'http://localhost:1996'
357
+ const validator = <T extends TProperties>(
358
+ schema: TObject<T>,
359
+ object: unknown
360
+ ) => {
361
+ const validate = ajv.compile(schema)
362
+
363
+ return validate(object)
364
+ }
365
+ const baseResponseDto = Type.Object({
366
+ error: Type.Boolean(),
367
+ message: Type.String()
368
+ })
369
+
370
+ type BaseResponseDTO = Static<typeof baseResponseDto>
371
+
372
+ describe('Simba.js tests', () => {
373
+ beforeAll(async () => {
374
+ await Server.start()
375
+ })
376
+
377
+ describe('API endpoints tests', () => {
378
+ let userID = ''
379
+
380
+ describe('API: GET /', () => {
381
+ let data: BaseResponseDTO
382
+
383
+ test('Should return 200 as status code', async () => {
384
+ const result = await axios.get<BaseResponseDTO>(BASE_URL)
385
+
386
+ data = result.data
387
+ expect(result.status).toBe(200)
388
+ })
389
+
390
+ test('Should be a successfully operation', () => {
391
+ expect(data.error).toBe(false)
392
+ })
393
+
394
+ test('Should be return baseResponseDto', () => {
395
+ expect(validator(baseResponseDto, data)).toBe(true)
396
+ })
397
+ })
398
+
399
+ describe('API: POST /api/users', () => {
400
+ const storeUserResponse = Type.Object({
401
+ error: Type.Boolean(),
402
+ message: userDto
403
+ })
404
+
405
+ type StoreUserDTO = Static<typeof storeUserResponse>
406
+
407
+ let data: StoreUserDTO
408
+ let status: number
409
+
410
+ test('Should return 201 as status code', async () => {
411
+ const result = await axios.post<StoreUserDTO>(\`\${BASE_URL}/api/users\`, {
412
+ args: {
413
+ lastName: 'Lzq',
414
+ name: 'Anthony'
415
+ }
416
+ })
417
+
418
+ data = result.data
419
+ status = result.status
420
+ userID = result.data.message.id ?? ''
421
+ expect(status).toBe(201)
422
+ })
423
+
424
+ test('Should be a successfully operation', () => {
425
+ expect(data.error).toBe(false)
426
+ })
427
+
428
+ test('Should return storeUserResponse', () => {
429
+ expect(validator(storeUserResponse, data)).toBe(true)
430
+ })
431
+ })
432
+
433
+ describe('API: GET /api/users', () => {
434
+ const getAllUsersResponse = Type.Object({
435
+ error: Type.Boolean(),
436
+ message: Type.Array(userDto)
437
+ })
438
+
439
+ type GetAllUsersDTO = Static<typeof getAllUsersResponse>
440
+
441
+ let data: GetAllUsersDTO
442
+ let status: number
443
+
444
+ test('Should return 200 as status code', async () => {
445
+ const result = await axios.get<GetAllUsersDTO>(\`\${BASE_URL}/api/users\`)
446
+
447
+ data = result.data
448
+ status = result.status
449
+ expect(status).toBe(200)
450
+ })
451
+
452
+ test('Should be a successfully operation', () => {
453
+ expect(data.error).toBe(false)
454
+ })
455
+
456
+ test('Should return getAllUsersResponse', () => {
457
+ expect(validator(getAllUsersResponse, data)).toBe(true)
458
+ })
459
+ })
460
+
461
+ describe('API: GET /api/user/:id', () => {
462
+ const getOneUserResponse = Type.Object({
463
+ error: Type.Boolean(),
464
+ message: userDto
465
+ })
466
+
467
+ type GetOneUserDTO = Static<typeof getOneUserResponse>
468
+
469
+ let data: GetOneUserDTO
470
+ let status: number
471
+
472
+ test('Should return 200 as status code', async () => {
473
+ const result = await axios.get<GetOneUserDTO>(
474
+ \`\${BASE_URL}/api/user/\${userID}\`
475
+ )
476
+
477
+ data = result.data
478
+ status = result.status
479
+ expect(status).toBe(200)
480
+ })
481
+
482
+ test('Should be a successfully operation', () => {
483
+ expect(data.error).toBe(false)
484
+ })
485
+
486
+ test('Should return getOneUserResponse', () => {
487
+ expect(validator(getOneUserResponse, data)).toBe(true)
488
+ })
489
+ })
490
+
491
+ describe('API: PATCH /api/user/:id', () => {
492
+ const updateUserResponse = Type.Object({
493
+ error: Type.Boolean(),
494
+ message: userDto
495
+ })
496
+
497
+ type UpdateUserDTO = Static<typeof updateUserResponse>
498
+
499
+ let data: UpdateUserDTO
500
+ let status: number
501
+
502
+ test('Should return 200 as status code', async () => {
503
+ const result = await axios.patch<UpdateUserDTO>(
504
+ \`\${BASE_URL}/api/user/\${userID}\`,
505
+ {
506
+ args: {
507
+ lastName: 'Luzquiños',
508
+ name: 'Anthony'
509
+ }
510
+ }
511
+ )
512
+
513
+ data = result.data
514
+ status = result.status
515
+ expect(status).toBe(200)
516
+ })
517
+
518
+ test('Should be a successfully operation', () => {
519
+ expect(data.error).toBe(false)
520
+ })
521
+
522
+ test('Should return updateUserResponse', () => {
523
+ expect(validator(updateUserResponse, data)).toBe(true)
524
+ })
525
+ })
526
+
527
+ describe('API: DELETE /api/user/:id', () => {
528
+ let data: BaseResponseDTO
529
+ let status: number
530
+
531
+ test('Should return 200 as status code', async () => {
532
+ const result = await axios.delete<BaseResponseDTO>(
533
+ \`\${BASE_URL}/api/user/\${userID}\`
534
+ )
535
+
536
+ data = result.data
537
+ status = result.status
538
+ expect(status).toBe(200)
539
+ })
540
+
541
+ test('Should be a successfully operation', () => {
542
+ expect(data.error).toBe(false)
543
+ })
544
+
545
+ test('Should return deleteUserResponse', () => {
546
+ expect(validator(baseResponseDto, data)).toBe(true)
547
+ })
548
+ })
549
+
550
+ describe('API: DELETE /api/users', () => {
551
+ let data: BaseResponseDTO
552
+ let status: number
553
+
554
+ test('Should return 200 as status code', async () => {
555
+ await axios.post(\`\${BASE_URL}/api/users\`, {
556
+ args: {
557
+ lastName: 'Lzq',
558
+ name: 'Anthony'
559
+ }
560
+ })
561
+
562
+ const result = await axios.delete<BaseResponseDTO>(
563
+ \`\${BASE_URL}/api/users\`
564
+ )
565
+
566
+ data = result.data
567
+ status = result.status
568
+ expect(status).toBe(200)
569
+ })
570
+
571
+ test('Should be a successfully operation', () => {
572
+ expect(data.error).toBe(false)
573
+ })
574
+
575
+ test('Should return deleteAllUsersResponse', () => {
576
+ expect(validator(baseResponseDto, data)).toBe(true)
577
+ })
578
+ })
579
+ })
580
+
581
+ afterAll(async () => {
582
+ await Server.stop()
583
+ })
584
+ })
585
+ `,
586
+ file: `${projectName}/test/index.test.ts`
587
+ },
588
+ jestGlobalSetup: {
589
+ content: `module.exports = () => {
590
+ if (process.env.NODE_ENV === 'local') require('./setEnvVars')
591
+ }
592
+ `,
593
+ file: `${projectName}/test/jestGlobalSetup.ts`
594
+ },
595
+ setEnvVars: {
596
+ content: `process.env.MONGO_URI =\n${
597
+ ENVIRONMENTS_WITH_MONGO_URI.includes(process.env.NODE_ENV)
598
+ ? ` '${process.env.MONGO_URI}'\n`
599
+ : ` 'mongodb://mongo:mongo@mongo:27017/${projectName}'\n`
600
+ }`,
601
+ file: `${projectName}/test/setEnvVars.ts`
602
+ }
603
+ }
604
+ }
605
+
606
+ await Promise.all([
607
+ writeFile(data.jestConfig.file, data.jestConfig.content),
608
+ writeFile(data.test.index.file, data.test.index.content),
609
+ writeFile(
610
+ data.test.jestGlobalSetup.file,
611
+ data.test.jestGlobalSetup.content
612
+ ),
613
+ writeFile(data.test.setEnvVars.file, data.test.setEnvVars.content)
614
+ ])
615
+ }
package/lib/src/index.js CHANGED
@@ -15,6 +15,8 @@ const webpack = require('./functions/webpack')
15
15
  const docker = require('./functions/docker')
16
16
  const herokuF = require('./functions/heroku')
17
17
  const api = require('./functions/api')
18
+ const testsF = require('./functions/tests')
19
+ const ghatF = require('./functions/ghat')
18
20
 
19
21
  /**
20
22
  * @param {Number} process number of process
@@ -47,41 +49,37 @@ module.exports = async ({
47
49
  manager,
48
50
  mainFile,
49
51
  fastify,
50
- graphql
52
+ graphql,
53
+ tests,
54
+ ghat
51
55
  }) => {
52
56
  const process = 4
53
57
  let i = 0
54
-
55
58
  const options = setOptions(process)
56
59
  const bar = new cliProgress.SingleBar(
57
60
  options,
58
61
  cliProgress.Presets.shades_classic
59
62
  )
60
-
61
63
  const expressProdPackages = `express swagger-ui-express cors express-pino-logger ${
62
64
  graphql ? 'apollo-server-express' : ''
63
65
  }`
64
-
65
66
  const fastifyProdPackages = `fastify@^3 @fastify/swagger@^6 @fastify/cors@^7 ${
66
67
  graphql ? 'apollo-server-fastify apollo-server-plugin-base' : ''
67
68
  }`
68
-
69
69
  const prodPackages = `${manager} @sinclair/typebox http-errors mongoose pino-pretty ${
70
70
  graphql
71
71
  ? '@graphql-tools/schema ajv ajv-formats apollo-server-core graphql'
72
72
  : 'ajv@^6'
73
73
  } ${fastify ? fastifyProdPackages : expressProdPackages}`
74
-
75
74
  const expressDevPackages =
76
75
  '@types/express @types/swagger-ui-express @types/cors @types/express-pino-logger'
77
-
78
76
  const fastifyDevPackages = ''
79
-
80
- const devPackages = `${manager} -D \
77
+ let devPackages = `${manager} -D \
81
78
  @types/http-errors \
82
79
  @types/node \
83
80
  @typescript-eslint/eslint-plugin \
84
81
  @typescript-eslint/parser \
82
+ axios \
85
83
  dotenv \
86
84
  eslint \
87
85
  eslint-config-prettier \
@@ -100,8 +98,14 @@ tsconfig-paths-webpack-plugin \
100
98
  typescript \
101
99
  webpack \
102
100
  webpack-cli \
103
- webpack-node-externals \
104
- ${fastify ? fastifyDevPackages : expressDevPackages}`
101
+ webpack-node-externals`
102
+
103
+ devPackages += ` ${fastify ? fastifyDevPackages : expressDevPackages}`
104
+
105
+ if (tests)
106
+ devPackages += ` @jest/types @types/jest eslint-plugin-jest jest jest-unit ts-jest`
107
+
108
+ if (manager === 'yarn add') devPackages += ` eslint-plugin-n`
105
109
 
106
110
  bar.start(process, i)
107
111
 
@@ -116,7 +120,8 @@ ${fastify ? fastifyDevPackages : expressDevPackages}`
116
120
  projectDescription,
117
121
  projectVersion: version,
118
122
  license,
119
- mainFile
123
+ mainFile,
124
+ tests
120
125
  }),
121
126
  readme(projectName, projectDescription),
122
127
  changelog(projectName),
@@ -143,6 +148,10 @@ ${fastify ? fastifyDevPackages : expressDevPackages}`
143
148
 
144
149
  if (heroku) functions.push(herokuF(projectName))
145
150
 
151
+ if (tests) functions.push(testsF(projectName, graphql))
152
+
153
+ if (ghat) functions.push(ghatF(projectName, manager))
154
+
146
155
  await Promise.all(functions)
147
156
  bar.update(++i)
148
157
 
@@ -173,6 +182,8 @@ ${fastify ? fastifyDevPackages : expressDevPackages}`
173
182
  * @property {String} mainFile main file of the project
174
183
  * @property {Boolean} fastify true means that the project will be using Fastify
175
184
  * @property {Boolean} graphql true means that the project will be using GraphQL
185
+ * @property {Boolean} tests true means that the project will have a basic suit of tests with Jest
186
+ * @property {Boolean} ghat true means that the project will have a GitHub Action for the suit of tests
176
187
  */
177
188
 
178
189
  /**
@@ -0,0 +1,3 @@
1
+ const ENVIRONMENTS_WITH_MONGO_URI = ['ci', 'local']
2
+
3
+ module.exports = { ENVIRONMENTS_WITH_MONGO_URI }