nest-monitor 0.3.4 → 0.4.1
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.
- package/Readme.md +21 -0
- package/dist/auto-test/auto-test.service.d.ts +2 -0
- package/dist/auto-test/auto-test.service.js +42 -20
- package/dist/interfaces/auto-test-option.interface.d.ts +10 -0
- package/dist/interfaces/auto-test-option.interface.js +12 -0
- package/dist/interfaces/auto-test-request.interface.d.ts +15 -0
- package/dist/interfaces/auto-test-request.interface.js +2 -0
- package/dist/utils/generate-test-filename.util.d.ts +1 -0
- package/dist/utils/generate-test-filename.util.js +13 -0
- package/dist/utils/load-config.util.d.ts +2 -0
- package/dist/utils/load-config.util.js +25 -0
- package/dist/utils/load-test.cases.util.d.ts +2 -0
- package/dist/utils/load-test.cases.util.js +68 -0
- package/package.json +1 -1
package/Readme.md
CHANGED
|
@@ -1 +1,22 @@
|
|
|
1
1
|
Это моя библиотека nest-monitor
|
|
2
|
+
|
|
3
|
+
## Конфигурация
|
|
4
|
+
|
|
5
|
+
Создайте файл `.nestmonitor` в корне проекта:
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
// .nestmonitor
|
|
9
|
+
module.exports = {
|
|
10
|
+
enabled: true,
|
|
11
|
+
host: '127.0.0.1',
|
|
12
|
+
port: 3000,
|
|
13
|
+
timeoutMs: 5000,
|
|
14
|
+
logSuccess: true,
|
|
15
|
+
logErrors: true
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
## Соглашение об именах файлов
|
|
19
|
+
Для эндпоинта с путём /api/users/:id -> файл:
|
|
20
|
+
```
|
|
21
|
+
api-users-id.test.ts
|
|
22
|
+
```
|
|
@@ -4,7 +4,9 @@ import { IAutoTestEndpoint } from '../interfaces/auto-test-endpoint.interface';
|
|
|
4
4
|
export declare class AutoTestService implements OnApplicationBootstrap {
|
|
5
5
|
private readonly httpService;
|
|
6
6
|
private endpoints;
|
|
7
|
+
private readonly options;
|
|
7
8
|
constructor(httpService: HttpService);
|
|
9
|
+
private testEndpointUrl;
|
|
8
10
|
registerControllerEndpoints(controllerName: string, endpoints: IAutoTestEndpoint[]): void;
|
|
9
11
|
onApplicationBootstrap(): Promise<void>;
|
|
10
12
|
}
|
|
@@ -13,10 +13,32 @@ exports.AutoTestService = void 0;
|
|
|
13
13
|
const axios_1 = require("@nestjs/axios");
|
|
14
14
|
const common_1 = require("@nestjs/common");
|
|
15
15
|
const rxjs_1 = require("rxjs");
|
|
16
|
+
const load_config_util_1 = require("../utils/load-config.util");
|
|
17
|
+
const load_test_cases_util_1 = require("../utils/load-test.cases.util");
|
|
16
18
|
let AutoTestService = class AutoTestService {
|
|
17
19
|
constructor(httpService) {
|
|
18
20
|
this.httpService = httpService;
|
|
19
21
|
this.endpoints = [];
|
|
22
|
+
this.options = (0, load_config_util_1.loadAutoTestConfig)();
|
|
23
|
+
}
|
|
24
|
+
async testEndpointUrl(url) {
|
|
25
|
+
const { timeoutMs, logSuccess, logErrors } = this.options;
|
|
26
|
+
const start = Date.now();
|
|
27
|
+
try {
|
|
28
|
+
await (0, rxjs_1.firstValueFrom)(this.httpService.get(url, { timeout: timeoutMs }));
|
|
29
|
+
const duration = Date.now() - start;
|
|
30
|
+
if (logSuccess) {
|
|
31
|
+
console.log(` ✅ ${url} → OK (${duration}мс)`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const axiosError = error;
|
|
36
|
+
const duration = Date.now() - start;
|
|
37
|
+
const status = axiosError.response?.status || '???';
|
|
38
|
+
if (logErrors) {
|
|
39
|
+
console.error(` ❌ ${url} → ${status} (${duration}мс)`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
20
42
|
}
|
|
21
43
|
registerControllerEndpoints(controllerName, endpoints) {
|
|
22
44
|
if (endpoints.length > 0) {
|
|
@@ -24,34 +46,34 @@ let AutoTestService = class AutoTestService {
|
|
|
24
46
|
}
|
|
25
47
|
}
|
|
26
48
|
async onApplicationBootstrap() {
|
|
27
|
-
if (this.endpoints.length === 0) {
|
|
28
|
-
console.log('[AutoTest] Нет контроллеров для автоматического тестирования.');
|
|
49
|
+
if (!this.options.enabled || this.endpoints.length === 0) {
|
|
29
50
|
return;
|
|
30
51
|
}
|
|
31
|
-
|
|
32
|
-
const host = process.env.HOST || '127.0.0.1';
|
|
33
|
-
const port = process.env.PORT || '3000';
|
|
52
|
+
const { host, port } = this.options;
|
|
34
53
|
const baseUrl = `http://${host}:${port}`;
|
|
54
|
+
console.log(`\n[AutoTest] Запуск тестов для ${this.endpoints.length} контроллеров...\n`);
|
|
35
55
|
for (const { controllerName, endpoints } of this.endpoints) {
|
|
36
|
-
console.log(
|
|
37
|
-
for (const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
console.log(`🔍 Контроллер: ${controllerName}`);
|
|
57
|
+
for (const { path } of endpoints) {
|
|
58
|
+
const hasParams = path.includes(':') || path.includes('*');
|
|
59
|
+
if (hasParams) {
|
|
60
|
+
const testCases = await (0, load_test_cases_util_1.loadTestCases)(path);
|
|
61
|
+
if (testCases.length === 0) {
|
|
62
|
+
console.log(` ⚠️ Пропущен (нет .test.js): ${path}`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
for (const testCase of testCases) {
|
|
66
|
+
const url = `${baseUrl}${testCase.path}`;
|
|
67
|
+
await this.testEndpointUrl(url);
|
|
68
|
+
}
|
|
45
69
|
}
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const message = error.message || 'Неизвестная ошибка';
|
|
50
|
-
console.error(` ❌ ОШИБКА: статус ${status} (${duration}мс) — ${message}`);
|
|
70
|
+
else {
|
|
71
|
+
const url = `${baseUrl}${path}`;
|
|
72
|
+
await this.testEndpointUrl(url);
|
|
51
73
|
}
|
|
52
74
|
}
|
|
53
75
|
}
|
|
54
|
-
console.log('\n[AutoTest]
|
|
76
|
+
console.log('\n[AutoTest] Завершено.\n');
|
|
55
77
|
}
|
|
56
78
|
};
|
|
57
79
|
exports.AutoTestService = AutoTestService;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface IAutoTestOptions {
|
|
2
|
+
enabled?: boolean;
|
|
3
|
+
host?: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
logSuccess?: boolean;
|
|
7
|
+
logErrors?: boolean;
|
|
8
|
+
autoTestDir?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const DEFAULT_AUTO_TEST_OPTIONS: Required<IAutoTestOptions>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_AUTO_TEST_OPTIONS = void 0;
|
|
4
|
+
exports.DEFAULT_AUTO_TEST_OPTIONS = {
|
|
5
|
+
enabled: true,
|
|
6
|
+
host: '127.0.0.1',
|
|
7
|
+
port: 3000,
|
|
8
|
+
timeoutMs: 5000,
|
|
9
|
+
logSuccess: true,
|
|
10
|
+
logErrors: true,
|
|
11
|
+
autoTestDir: './auto-test',
|
|
12
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface IAutoTestRequest {
|
|
2
|
+
/**
|
|
3
|
+
* Полный путь для запроса (уже с подставленными параметрами)
|
|
4
|
+
* Пример: "/api/users/123"
|
|
5
|
+
*/
|
|
6
|
+
path: string;
|
|
7
|
+
/**
|
|
8
|
+
* Необязательные query-параметры
|
|
9
|
+
*/
|
|
10
|
+
query?: Record<string, string | number | boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Необязательные заголовки
|
|
13
|
+
*/
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateTestFilename(route: string): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateTestFilename = generateTestFilename;
|
|
4
|
+
function generateTestFilename(route) {
|
|
5
|
+
return ((route
|
|
6
|
+
.replace(/^\/+/, '')
|
|
7
|
+
.replace(/:/g, '-')
|
|
8
|
+
.replace(/\//g, '-')
|
|
9
|
+
.replace(/[^a-zA-Z0-9\-_]/g, '')
|
|
10
|
+
.replace(/-+/g, '-')
|
|
11
|
+
.replace(/^-|-$/g, '')
|
|
12
|
+
.toLowerCase() || 'root') + '.test.js');
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadAutoTestConfig = loadAutoTestConfig;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const auto_test_option_interface_1 = require("../interfaces/auto-test-option.interface");
|
|
7
|
+
function loadAutoTestConfig() {
|
|
8
|
+
const configPath = (0, path_1.join)(process.cwd(), '.nestmonitor');
|
|
9
|
+
let userConfig = {};
|
|
10
|
+
try {
|
|
11
|
+
const rawContent = (0, fs_1.readFileSync)(configPath, 'utf8');
|
|
12
|
+
const parsed = JSON.parse(rawContent);
|
|
13
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
14
|
+
userConfig = parsed;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
// Файл не существует или содержит невалидный JSON — используем значения по умолчанию
|
|
19
|
+
console.warn(`[AutoTest] Не удалось загрузить конфиг-файл, error: ${e}`);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
...auto_test_option_interface_1.DEFAULT_AUTO_TEST_OPTIONS,
|
|
23
|
+
...userConfig,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadTestCases = loadTestCases;
|
|
37
|
+
const fs_1 = require("fs");
|
|
38
|
+
const path_1 = require("path");
|
|
39
|
+
const generate_test_filename_util_1 = require("./generate-test-filename.util");
|
|
40
|
+
const TEST_DIR = '.auto-test';
|
|
41
|
+
async function loadTestCases(route) {
|
|
42
|
+
const filename = (0, generate_test_filename_util_1.generateTestFilename)(route);
|
|
43
|
+
const fullPath = (0, path_1.join)(process.cwd(), TEST_DIR, filename);
|
|
44
|
+
if (!existsWithCaseSync(fullPath)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
delete require.cache[require.resolve(fullPath)];
|
|
49
|
+
const mod = await Promise.resolve(`${fullPath}`).then(s => __importStar(require(s)));
|
|
50
|
+
return Array.isArray(mod.testCases) ? mod.testCases : [];
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
54
|
+
console.warn(`[AutoTest] Не удалось загрузить тест-файл: ${fullPath}, error: ${e}`);
|
|
55
|
+
}
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function existsWithCaseSync(path) {
|
|
60
|
+
if (!(0, fs_1.existsSync)(path))
|
|
61
|
+
return false;
|
|
62
|
+
const dir = path.substring(0, path.lastIndexOf('/'));
|
|
63
|
+
const basename = path.substring(dir.length + 1);
|
|
64
|
+
if (dir === '')
|
|
65
|
+
return true;
|
|
66
|
+
const files = (0, fs_1.readdirSync)(dir);
|
|
67
|
+
return files.includes(basename);
|
|
68
|
+
}
|
package/package.json
CHANGED