add test case

This commit is contained in:
liuyi 2025-05-27 14:07:54 +08:00
parent 1cfeb1f22e
commit a6dcfce339
7 changed files with 243 additions and 48 deletions

View File

@ -26,11 +26,11 @@
"@nestjs/platform-fastify": "^10.0.3", "@nestjs/platform-fastify": "^10.0.3",
"@nestjs/swagger": "^7.4.2", "@nestjs/swagger": "^7.4.2",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
"better-sqlite3": "^11.10.0",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.2", "class-validator": "^0.14.2",
"deepmerge": "^4.3.1", "deepmerge": "^4.3.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mysql2": "^3.14.1",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",

View File

@ -22,10 +22,7 @@ importers:
version: 7.4.2(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.17)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14) version: 7.4.2(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.17)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)
'@nestjs/typeorm': '@nestjs/typeorm':
specifier: ^11.0.0 specifier: ^11.0.0
version: 11.0.0(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.17)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.24(better-sqlite3@11.10.0)(reflect-metadata@0.1.14)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6))) version: 11.0.0(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.17)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.24(better-sqlite3@11.10.0)(mysql2@3.14.1)(reflect-metadata@0.1.14)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6)))
better-sqlite3:
specifier: ^11.10.0
version: 11.10.0
class-transformer: class-transformer:
specifier: ^0.5.1 specifier: ^0.5.1
version: 0.5.1 version: 0.5.1
@ -38,6 +35,9 @@ importers:
lodash: lodash:
specifier: ^4.17.21 specifier: ^4.17.21
version: 4.17.21 version: 4.17.21
mysql2:
specifier: ^3.14.1
version: 3.14.1
reflect-metadata: reflect-metadata:
specifier: ^0.1.13 specifier: ^0.1.13
version: 0.1.14 version: 0.1.14
@ -52,7 +52,7 @@ importers:
version: 2.17.0 version: 2.17.0
typeorm: typeorm:
specifier: ^0.3.24 specifier: ^0.3.24
version: 0.3.24(better-sqlite3@11.10.0)(reflect-metadata@0.1.14)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6)) version: 0.3.24(better-sqlite3@11.10.0)(mysql2@3.14.1)(reflect-metadata@0.1.14)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6))
validator: validator:
specifier: ^13.15.0 specifier: ^13.15.0
version: 13.15.0 version: 13.15.0
@ -1170,6 +1170,10 @@ packages:
avvio@8.4.0: avvio@8.4.0:
resolution: {integrity: sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==} resolution: {integrity: sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==}
aws-ssl-profiles@1.1.2:
resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==}
engines: {node: '>= 6.0.0'}
babel-jest@29.7.0: babel-jest@29.7.0:
resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -1561,6 +1565,10 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
denque@2.1.0:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'}
depd@2.0.0: depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -2080,6 +2088,9 @@ packages:
functions-have-names@1.2.3: functions-have-names@1.2.3:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
generate-function@2.3.1:
resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}
gensync@1.0.0-beta.2: gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -2224,6 +2235,10 @@ packages:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
ieee754@1.2.1: ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@ -2361,6 +2376,9 @@ packages:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
is-property@1.0.2:
resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
is-regex@1.2.1: is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -2686,6 +2704,9 @@ packages:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'} engines: {node: '>=10'}
long@5.3.2:
resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
lowercase-keys@2.0.0: lowercase-keys@2.0.0:
resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -2699,6 +2720,14 @@ packages:
lru-cache@5.1.1: lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lru-cache@7.18.3:
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
engines: {node: '>=12'}
lru.min@1.1.2:
resolution: {integrity: sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==}
engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'}
magic-string@0.30.8: magic-string@0.30.8:
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -2818,6 +2847,14 @@ packages:
resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
mysql2@3.14.1:
resolution: {integrity: sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==}
engines: {node: '>= 8.0'}
named-placeholders@1.1.3:
resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==}
engines: {node: '>=12.0.0'}
nanoid@3.3.11: nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@ -3354,6 +3391,9 @@ packages:
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
seq-queue@0.0.5:
resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==}
serialize-javascript@6.0.2: serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
@ -3475,6 +3515,10 @@ packages:
resolution: {integrity: sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw==} resolution: {integrity: sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw==}
engines: {node: '>=14'} engines: {node: '>=14'}
sqlstring@2.3.3:
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
engines: {node: '>= 0.6'}
stack-utils@2.0.6: stack-utils@2.0.6:
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -4685,13 +4729,13 @@ snapshots:
optionalDependencies: optionalDependencies:
'@nestjs/platform-express': 10.4.17(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.17) '@nestjs/platform-express': 10.4.17(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.17)
'@nestjs/typeorm@11.0.0(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.17)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.24(better-sqlite3@11.10.0)(reflect-metadata@0.1.14)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6)))': '@nestjs/typeorm@11.0.0(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.17)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.24(better-sqlite3@11.10.0)(mysql2@3.14.1)(reflect-metadata@0.1.14)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6)))':
dependencies: dependencies:
'@nestjs/common': 10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/common': 10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2)
'@nestjs/core': 10.4.17(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.17)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.17(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.17)(reflect-metadata@0.1.14)(rxjs@7.8.2)
reflect-metadata: 0.1.14 reflect-metadata: 0.1.14
rxjs: 7.8.2 rxjs: 7.8.2
typeorm: 0.3.24(better-sqlite3@11.10.0)(reflect-metadata@0.1.14)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6)) typeorm: 0.3.24(better-sqlite3@11.10.0)(mysql2@3.14.1)(reflect-metadata@0.1.14)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6))
'@noble/hashes@1.8.0': {} '@noble/hashes@1.8.0': {}
@ -5277,6 +5321,8 @@ snapshots:
'@fastify/error': 3.4.1 '@fastify/error': 3.4.1
fastq: 1.19.1 fastq: 1.19.1
aws-ssl-profiles@1.1.2: {}
babel-jest@29.7.0(@babel/core@7.27.1): babel-jest@29.7.0(@babel/core@7.27.1):
dependencies: dependencies:
'@babel/core': 7.27.1 '@babel/core': 7.27.1
@ -5340,6 +5386,7 @@ snapshots:
dependencies: dependencies:
bindings: 1.5.0 bindings: 1.5.0
prebuild-install: 7.1.3 prebuild-install: 7.1.3
optional: true
bin-check@4.1.0: bin-check@4.1.0:
dependencies: dependencies:
@ -5362,6 +5409,7 @@ snapshots:
bindings@1.5.0: bindings@1.5.0:
dependencies: dependencies:
file-uri-to-path: 1.0.0 file-uri-to-path: 1.0.0
optional: true
bl@4.1.0: bl@4.1.0:
dependencies: dependencies:
@ -5495,7 +5543,8 @@ snapshots:
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
chownr@1.1.4: {} chownr@1.1.4:
optional: true
chrome-trace-event@1.0.4: {} chrome-trace-event@1.0.4: {}
@ -5686,7 +5735,8 @@ snapshots:
dedent@1.6.0: {} dedent@1.6.0: {}
deep-extend@0.6.0: {} deep-extend@0.6.0:
optional: true
deep-is@0.1.4: {} deep-is@0.1.4: {}
@ -5712,13 +5762,16 @@ snapshots:
delayed-stream@1.0.0: {} delayed-stream@1.0.0: {}
denque@2.1.0: {}
depd@2.0.0: depd@2.0.0:
optional: true optional: true
destroy@1.2.0: destroy@1.2.0:
optional: true optional: true
detect-libc@2.0.4: {} detect-libc@2.0.4:
optional: true
detect-newline@3.1.0: {} detect-newline@3.1.0: {}
@ -6105,7 +6158,8 @@ snapshots:
exit@0.1.2: {} exit@0.1.2: {}
expand-template@2.0.3: {} expand-template@2.0.3:
optional: true
expect@29.7.0: expect@29.7.0:
dependencies: dependencies:
@ -6263,7 +6317,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
file-uri-to-path@1.0.0: {} file-uri-to-path@1.0.0:
optional: true
filename-reserved-regex@3.0.0: {} filename-reserved-regex@3.0.0: {}
@ -6363,7 +6418,8 @@ snapshots:
fresh@0.5.2: fresh@0.5.2:
optional: true optional: true
fs-constants@1.0.0: {} fs-constants@1.0.0:
optional: true
fs-extra@10.1.0: fs-extra@10.1.0:
dependencies: dependencies:
@ -6391,6 +6447,10 @@ snapshots:
functions-have-names@1.2.3: {} functions-have-names@1.2.3: {}
generate-function@2.3.1:
dependencies:
is-property: 1.0.2
gensync@1.0.0-beta.2: {} gensync@1.0.0-beta.2: {}
get-caller-file@2.0.5: {} get-caller-file@2.0.5: {}
@ -6429,7 +6489,8 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0
github-from-package@0.0.0: {} github-from-package@0.0.0:
optional: true
glob-parent@5.1.2: glob-parent@5.1.2:
dependencies: dependencies:
@ -6554,6 +6615,10 @@ snapshots:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
iconv-lite@0.6.3:
dependencies:
safer-buffer: 2.1.2
ieee754@1.2.1: {} ieee754@1.2.1: {}
ignore@5.3.2: {} ignore@5.3.2: {}
@ -6577,7 +6642,8 @@ snapshots:
inherits@2.0.4: {} inherits@2.0.4: {}
ini@1.3.8: {} ini@1.3.8:
optional: true
inquirer@8.2.6: inquirer@8.2.6:
dependencies: dependencies:
@ -6707,6 +6773,8 @@ snapshots:
is-plain-object@5.0.0: {} is-plain-object@5.0.0: {}
is-property@1.0.2: {}
is-regex@1.2.1: is-regex@1.2.1:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
@ -7216,6 +7284,8 @@ snapshots:
chalk: 4.1.2 chalk: 4.1.2
is-unicode-supported: 0.1.0 is-unicode-supported: 0.1.0
long@5.3.2: {}
lowercase-keys@2.0.0: {} lowercase-keys@2.0.0: {}
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
@ -7229,6 +7299,10 @@ snapshots:
dependencies: dependencies:
yallist: 3.1.1 yallist: 3.1.1
lru-cache@7.18.3: {}
lru.min@1.1.2: {}
magic-string@0.30.8: magic-string@0.30.8:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
@ -7297,7 +7371,8 @@ snapshots:
minipass@7.1.2: {} minipass@7.1.2: {}
mkdirp-classic@0.5.3: {} mkdirp-classic@0.5.3:
optional: true
mkdirp@0.5.6: mkdirp@0.5.6:
dependencies: dependencies:
@ -7328,9 +7403,26 @@ snapshots:
mute-stream@1.0.0: {} mute-stream@1.0.0: {}
mysql2@3.14.1:
dependencies:
aws-ssl-profiles: 1.1.2
denque: 2.1.0
generate-function: 2.3.1
iconv-lite: 0.6.3
long: 5.3.2
lru.min: 1.1.2
named-placeholders: 1.1.3
seq-queue: 0.0.5
sqlstring: 2.3.3
named-placeholders@1.1.3:
dependencies:
lru-cache: 7.18.3
nanoid@3.3.11: {} nanoid@3.3.11: {}
napi-build-utils@2.0.0: {} napi-build-utils@2.0.0:
optional: true
natural-compare-lite@1.4.0: {} natural-compare-lite@1.4.0: {}
@ -7344,6 +7436,7 @@ snapshots:
node-abi@3.75.0: node-abi@3.75.0:
dependencies: dependencies:
semver: 7.7.1 semver: 7.7.1
optional: true
node-abort-controller@3.1.1: {} node-abort-controller@3.1.1: {}
@ -7590,6 +7683,7 @@ snapshots:
simple-get: 4.0.1 simple-get: 4.0.1
tar-fs: 2.1.2 tar-fs: 2.1.2
tunnel-agent: 0.6.0 tunnel-agent: 0.6.0
optional: true
prelude-ls@1.2.1: {} prelude-ls@1.2.1: {}
@ -7671,6 +7765,7 @@ snapshots:
ini: 1.3.8 ini: 1.3.8
minimist: 1.2.8 minimist: 1.2.8
strip-json-comments: 2.0.1 strip-json-comments: 2.0.1
optional: true
react-is@18.3.1: {} react-is@18.3.1: {}
@ -7879,6 +7974,8 @@ snapshots:
- supports-color - supports-color
optional: true optional: true
seq-queue@0.0.5: {}
serialize-javascript@6.0.2: serialize-javascript@6.0.2:
dependencies: dependencies:
randombytes: 2.1.0 randombytes: 2.1.0
@ -7969,13 +8066,15 @@ snapshots:
signal-exit@4.1.0: {} signal-exit@4.1.0: {}
simple-concat@1.0.1: {} simple-concat@1.0.1:
optional: true
simple-get@4.0.1: simple-get@4.0.1:
dependencies: dependencies:
decompress-response: 6.0.0 decompress-response: 6.0.0
once: 1.4.0 once: 1.4.0
simple-concat: 1.0.1 simple-concat: 1.0.1
optional: true
sisteransi@1.0.5: {} sisteransi@1.0.5: {}
@ -8015,6 +8114,8 @@ snapshots:
sql-highlight@6.0.0: {} sql-highlight@6.0.0: {}
sqlstring@2.3.3: {}
stack-utils@2.0.6: stack-utils@2.0.6:
dependencies: dependencies:
escape-string-regexp: 2.0.0 escape-string-regexp: 2.0.0
@ -8090,7 +8191,8 @@ snapshots:
strip-final-newline@2.0.0: {} strip-final-newline@2.0.0: {}
strip-json-comments@2.0.1: {} strip-json-comments@2.0.1:
optional: true
strip-json-comments@3.1.1: {} strip-json-comments@3.1.1: {}
@ -8150,6 +8252,7 @@ snapshots:
mkdirp-classic: 0.5.3 mkdirp-classic: 0.5.3
pump: 3.0.2 pump: 3.0.2
tar-stream: 2.2.0 tar-stream: 2.2.0
optional: true
tar-stream@2.2.0: tar-stream@2.2.0:
dependencies: dependencies:
@ -8158,6 +8261,7 @@ snapshots:
fs-constants: 1.0.0 fs-constants: 1.0.0
inherits: 2.0.4 inherits: 2.0.4
readable-stream: 3.6.2 readable-stream: 3.6.2
optional: true
terser-webpack-plugin@5.3.14(@swc/core@1.11.24)(webpack@5.97.1(@swc/core@1.11.24)): terser-webpack-plugin@5.3.14(@swc/core@1.11.24)(webpack@5.97.1(@swc/core@1.11.24)):
dependencies: dependencies:
@ -8303,6 +8407,7 @@ snapshots:
tunnel-agent@0.6.0: tunnel-agent@0.6.0:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
optional: true
type-check@0.4.0: type-check@0.4.0:
dependencies: dependencies:
@ -8356,7 +8461,7 @@ snapshots:
typedarray@0.0.6: typedarray@0.0.6:
optional: true optional: true
typeorm@0.3.24(better-sqlite3@11.10.0)(reflect-metadata@0.1.14)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6)): typeorm@0.3.24(better-sqlite3@11.10.0)(mysql2@3.14.1)(reflect-metadata@0.1.14)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6)):
dependencies: dependencies:
'@sqltools/formatter': 1.2.5 '@sqltools/formatter': 1.2.5
ansis: 3.17.0 ansis: 3.17.0
@ -8375,6 +8480,7 @@ snapshots:
yargs: 17.7.2 yargs: 17.7.2
optionalDependencies: optionalDependencies:
better-sqlite3: 11.10.0 better-sqlite3: 11.10.0
mysql2: 3.14.1
ts-node: 10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6) ts-node: 10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.1.6)
transitivePeerDependencies: transitivePeerDependencies:
- babel-plugin-macros - babel-plugin-macros

View File

@ -11,4 +11,5 @@ export const database = (): TypeOrmModuleOptions => ({
database: '3r', database: '3r',
synchronize: true, synchronize: true,
autoLoadEntities: true, autoLoadEntities: true,
timezone: '+08:00',
}); });

View File

@ -6,6 +6,7 @@ import {
Entity, Entity,
JoinTable, JoinTable,
ManyToMany, ManyToMany,
ManyToOne,
OneToMany, OneToMany,
PrimaryColumn, PrimaryColumn,
Relation, Relation,
@ -67,7 +68,7 @@ export class PostEntity extends BaseEntity {
commentCount: number; commentCount: number;
@Expose() @Expose()
@OneToMany(() => CategoryEntity, (category) => category.posts, { @ManyToOne(() => CategoryEntity, (category) => category.posts, {
nullable: true, nullable: true,
onDelete: 'SET NULL', onDelete: 'SET NULL',
}) })

View File

@ -10,11 +10,9 @@ import {
QueryCommentTreeDto, QueryCommentTreeDto,
} from '@/modules/content/dtos/comment.dto'; } from '@/modules/content/dtos/comment.dto';
import { CommentEntity } from '@/modules/content/entities/comment.entity'; import { CommentEntity } from '@/modules/content/entities/comment.entity';
import { CommentRepository, PostRepository } from '@/modules/content/repositories';
import { treePaginate } from '@/modules/database/utils'; import { treePaginate } from '@/modules/database/utils';
import { CommentRepository } from '../repositories/comment.repository';
import { PostRepository } from '../repositories/post.repository';
@Injectable() @Injectable()
export class CommentService { export class CommentService {
constructor( constructor(
@ -82,7 +80,7 @@ export class CommentService {
} }
parent = await this.repository.findOne({ parent = await this.repository.findOne({
where: { id }, where: { id },
relations: ['parent', 'children'], relations: ['parent', 'children', 'post'],
}); });
if (!parent) { if (!parent) {
throw new EntityNotFoundError(CommentEntity, `Parent Comment ${id} not found`); throw new EntityNotFoundError(CommentEntity, `Parent Comment ${id} not found`);

View File

@ -3,26 +3,20 @@ import { describe } from 'node:test';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { omit, pick } from 'lodash'; import { pick } from 'lodash';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { database } from '@/config'; import { database } from '@/config';
import { ContentModule } from '@/modules/content/content.module'; import { ContentModule } from '@/modules/content/content.module';
import { CategoryEntity, CommentEntity, PostEntity, TagEntity } from '@/modules/content/entities'; import { CategoryEntity, CommentEntity, PostEntity, TagEntity } from '@/modules/content/entities';
import { CategoryRepository, PostRepository, TagRepository } from '@/modules/content/repositories';
import { DatabaseModule } from '@/modules/database/database.module'; import { DatabaseModule } from '@/modules/database/database.module';
import { CommentRepository } from '../src/modules/content/repositories/comment.repository'; import { generateRandomNumber, generateUniqueRandomNumbers } from './generate-mock-data';
import { commentData, INIT_DATA, initialCategories, postData, tagData } from './test-data'; import { commentData, INIT_DATA, initialCategories, postData, tagData } from './test-data';
describe('category test', () => { describe('category test', () => {
let datasource: DataSource; let datasource: DataSource;
let app: NestFastifyApplication; let app: NestFastifyApplication;
let categoryRepository: CategoryRepository;
let tagRepository: TagRepository;
let postRepository: PostRepository;
let commentRepository: CommentRepository;
beforeAll(async () => { beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
@ -32,16 +26,31 @@ describe('category test', () => {
await app.init(); await app.init();
await app.getHttpAdapter().getInstance().ready(); await app.getHttpAdapter().getInstance().ready();
categoryRepository = module.get<CategoryRepository>(CategoryRepository);
tagRepository = module.get<TagRepository>(TagRepository);
postRepository = module.get<PostRepository>(PostRepository);
commentRepository = module.get<CommentRepository>(CommentRepository);
datasource = module.get<DataSource>(DataSource); datasource = module.get<DataSource>(DataSource);
if (!datasource.isInitialized) {
await datasource.initialize();
}
});
beforeEach(async () => {
if (INIT_DATA) { if (INIT_DATA) {
await categoryRepository.deleteAll(); const queryRunner = datasource.createQueryRunner();
await postRepository.deleteAll(); try {
await tagRepository.deleteAll(); await queryRunner.query('SET FOREIGN_KEY_CHECKS = 0');
await commentRepository.deleteAll();
datasource.entityMetadatas.map(async (entity) => {
const table = entity.schema
? `${entity.schema}.${entity.tableName}`
: `${entity.tableName}`;
console.log(`TRUNCATE TABLE ${table}`);
await queryRunner.query(`TRUNCATE TABLE ${table}`);
return table;
});
// await queryRunner.query(`TRUNCATE TABLE ${tables}`);
} finally {
await queryRunner.query('SET FOREIGN_KEY_CHECKS = 1');
await queryRunner.release();
}
// init category data // init category data
const categories = await addCategory(app, initialCategories); const categories = await addCategory(app, initialCategories);
@ -50,14 +59,21 @@ describe('category test', () => {
const tags = await addTag(app, tagData); const tags = await addTag(app, tagData);
console.log(tags); console.log(tags);
// init post data // init post data
addPost( const posts = await addPost(
app, app,
postData, postData,
tags.map((tag) => tag.id), tags.map((tag) => tag.id),
categories.map((category) => category.id), categories.map((category) => category.id),
); );
console.log(posts);
console.log('='.repeat(100));
// init comment data // init comment data
addComment(app, commentData); const comments = await addComment(
app,
commentData,
posts.map((post) => post.id),
);
console.log(comments);
} }
}); });
@ -120,11 +136,13 @@ async function addPost(
if (app && data && data.length > 0) { if (app && data && data.length > 0) {
for (let index = 0; index < data.length; index++) { for (let index = 0; index < data.length; index++) {
const item = data[index]; const item = data[index];
// TODO add tag and category item.category = categories[generateRandomNumber(1, categories.length - 1)[0]];
item.tags = generateUniqueRandomNumbers(0, tags.length - 1, 3).map((idx) => tags[idx]);
// console.log(JSON.stringify(item));
const result = await app.inject({ const result = await app.inject({
method: 'POST', method: 'POST',
url: '/post', url: '/posts',
body: omit(item, ['tags', 'category']), body: item,
}); });
const addedItem: PostEntity = result.json(); const addedItem: PostEntity = result.json();
posts.push(addedItem); posts.push(addedItem);
@ -136,17 +154,32 @@ async function addPost(
async function addComment( async function addComment(
app: NestFastifyApplication, app: NestFastifyApplication,
data: RecordAny[], data: RecordAny[],
posts: string[],
): Promise<CommentEntity[]> { ): Promise<CommentEntity[]> {
const comments: CommentEntity[] = []; const comments: CommentEntity[] = [];
if (app && data && data.length > 0) { if (app && data && data.length > 0) {
for (let index = 0; index < data.length; index++) { for (let index = 0; index < data.length; index++) {
const item = data[index]; const item = data[index];
item.post = posts[generateRandomNumber(0, posts.length - 1)[0]];
const commentsFilter = comments
.filter((comment) => comment.post === item.post)
.map((comment) => comment.id);
console.log('A'.repeat(100));
console.log(commentsFilter);
item.parent =
commentsFilter.length > 0
? commentsFilter[generateRandomNumber(0, commentsFilter.length - 1)[0]]
: undefined;
console.log(JSON.stringify(item));
const result = await app.inject({ const result = await app.inject({
method: 'POST', method: 'POST',
url: '/comment', url: '/comment',
body: item, body: item,
}); });
const addedItem: CommentEntity = result.json(); const addedItem = result.json();
console.log(addedItem);
addedItem.post = item.post;
comments.push(addedItem); comments.push(addedItem);
} }
} }

View File

@ -35,3 +35,59 @@ export function generateMockComment(): CreateCommentDto {
parent: fakerEN.string.uuid(), parent: fakerEN.string.uuid(),
}; };
} }
/**
*
* @param start
* @param end
* @param n
* @returns n [start, end]
* @throws
*/
export function generateUniqueRandomNumbers(start: number, end: number, n: number): number[] {
// 处理反向范围并计算实际区间
const [min, max] = start <= end ? [start, end] : [end, start];
const range = max - min + 1;
if (n <= 0 || !Number.isInteger(n)) {
throw new Error('参数 n 必须是正整数');
}
if (n === 1) {
return generateRandomNumber(start, end);
}
// 参数校验:请求数量不能超过可用唯一值总数
if (n > range) {
throw new Error(
`Cannot generate ${n} unique numbers in range [${min}, ${max}]. Maximum possible: ${range}`,
);
}
// 生成所有可能的候选数字
const candidates = Array.from({ length: range }, (_, i) => min + i);
// Fisher-Yates 洗牌算法随机打乱数组
for (let i = candidates.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[candidates[i], candidates[j]] = [candidates[j], candidates[i]];
}
// 返回前 n 个元素
return candidates.slice(0, n);
}
export function generateRandomNumber(start: number, end: number): number[] {
// 处理反向范围(确保 min <= max
const [min, max] = start <= end ? [start, end] : [end, start];
if (min === max) {
return [min];
}
// 生成 1 个随机整数
return Array.from({ length: 1 }, () => {
// 随机数公式Math.floor(Math.random() * (max - min + 1)) + min
return Math.floor(Math.random() * (max - min + 1)) + min;
});
}