@solidxai/core 0.1.9 → 0.1.10-beta.2
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/dist/commands/run-tests.command.d.ts +2 -0
- package/dist/commands/run-tests.command.d.ts.map +1 -1
- package/dist/commands/run-tests.command.js +49 -17
- package/dist/commands/run-tests.command.js.map +1 -1
- package/dist/controllers/user.controller.d.ts.map +1 -1
- package/dist/controllers/user.controller.js.map +1 -1
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/field-metadata.service.js +2 -2
- package/dist/services/field-metadata.service.js.map +1 -1
- package/dist/services/queues/database-subscriber.service.js +3 -3
- package/dist/services/queues/database-subscriber.service.js.map +1 -1
- package/dist/services/queues/rabbitmq-subscriber.service.js +4 -4
- package/dist/services/queues/rabbitmq-subscriber.service.js.map +1 -1
- package/dist/services/queues/redis-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/redis-subscriber.service.js +4 -1
- package/dist/services/queues/redis-subscriber.service.js.map +1 -1
- package/dist/solid-core.module.d.ts +1 -0
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +1 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/testing/reporter/webhook-reporter.d.ts +54 -0
- package/dist/testing/reporter/webhook-reporter.d.ts.map +1 -0
- package/dist/testing/reporter/webhook-reporter.js +74 -0
- package/dist/testing/reporter/webhook-reporter.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/run-tests.command.ts +45 -17
- package/src/controllers/user.controller.ts +16 -16
- package/src/services/crud.service.ts +2 -2
- package/src/services/field-metadata.service.ts +2 -2
- package/src/services/queues/database-subscriber.service.ts +6 -6
- package/src/services/queues/rabbitmq-subscriber.service.ts +5 -5
- package/src/services/queues/redis-subscriber.service.ts +5 -2
- package/src/solid-core.module.ts +1 -0
- package/src/testing/reporter/webhook-reporter.ts +116 -0
|
@@ -20,37 +20,37 @@ export class UserController {
|
|
|
20
20
|
@ApiBearerAuth("jwt")
|
|
21
21
|
@Post()
|
|
22
22
|
@UseInterceptors(AnyFilesInterceptor())
|
|
23
|
-
create(@Body() createDto: CreateUserDto, @UploadedFiles() files: Array<Express.Multer.File
|
|
24
|
-
return this.service.create(createDto, files,solidRequestContext);
|
|
23
|
+
create(@Body() createDto: CreateUserDto, @UploadedFiles() files: Array<Express.Multer.File>, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
|
|
24
|
+
return this.service.create(createDto, files, solidRequestContext);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
@ApiBearerAuth("jwt")
|
|
28
28
|
@Post('/bulk')
|
|
29
29
|
@UseInterceptors(AnyFilesInterceptor())
|
|
30
|
-
insertMany(@Body() createDtos: CreateUserDto[], @UploadedFiles() filesArray: Express.Multer.File[][] = []
|
|
31
|
-
return this.service.insertMany(createDtos, filesArray,solidRequestContext);
|
|
30
|
+
insertMany(@Body() createDtos: CreateUserDto[], @UploadedFiles() filesArray: Express.Multer.File[][] = [], @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
|
|
31
|
+
return this.service.insertMany(createDtos, filesArray, solidRequestContext);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
@ApiBearerAuth("jwt")
|
|
36
36
|
@Put(':id')
|
|
37
37
|
@UseInterceptors(AnyFilesInterceptor())
|
|
38
|
-
update(@Param('id') id: number, @Body() updateDto: UpdateUserDto, @UploadedFiles() files: Array<Express.Multer.File
|
|
39
|
-
return this.service.update(id, updateDto, files,false,solidRequestContext);
|
|
38
|
+
update(@Param('id') id: number, @Body() updateDto: UpdateUserDto, @UploadedFiles() files: Array<Express.Multer.File>, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
|
|
39
|
+
return this.service.update(id, updateDto, files, false, solidRequestContext);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
@ApiBearerAuth("jwt")
|
|
44
44
|
@Patch(':id/update-user-and-roles')
|
|
45
|
-
updateUser(@Param('id') id: number
|
|
46
|
-
return this.service.updateUser(id, updateDto, files,solidRequestContext);
|
|
45
|
+
updateUser(@Param('id') id: number, @Body() updateDto: any, @UploadedFiles() files: Array<Express.Multer.File>, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
|
|
46
|
+
return this.service.updateUser(id, updateDto, files, solidRequestContext);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
@ApiBearerAuth("jwt")
|
|
51
51
|
@Patch(':id')
|
|
52
52
|
@UseInterceptors(AnyFilesInterceptor())
|
|
53
|
-
partialUpdate(@Param('id') id: number, @Body() updateDto: UpdateUserDto, @UploadedFiles() files: Array<Express.Multer.File
|
|
53
|
+
partialUpdate(@Param('id') id: number, @Body() updateDto: UpdateUserDto, @UploadedFiles() files: Array<Express.Multer.File>, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
|
|
54
54
|
return this.service.update(id, updateDto, files, true, solidRequestContext);
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -67,8 +67,8 @@ export class UserController {
|
|
|
67
67
|
@ApiQuery({ name: 'populateMedia', required: false, type: Array })
|
|
68
68
|
@ApiQuery({ name: 'filters', required: false, type: Array })
|
|
69
69
|
@Get()
|
|
70
|
-
async findMany(@Query() query: any, @SolidRequestContextDecorator() solidRequestContext:SolidRequestContextDto) {
|
|
71
|
-
return this.service.find(query,solidRequestContext);
|
|
70
|
+
async findMany(@Query() query: any, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
|
|
71
|
+
return this.service.find(query, solidRequestContext);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
@ApiBearerAuth("jwt")
|
|
@@ -81,18 +81,18 @@ export class UserController {
|
|
|
81
81
|
|
|
82
82
|
@ApiBearerAuth("jwt")
|
|
83
83
|
@Get(':id')
|
|
84
|
-
async findOne(@Param('id') id: string, @Query() query: any
|
|
85
|
-
return this.service.findOne(+id, query,solidRequestContext);
|
|
84
|
+
async findOne(@Param('id') id: string, @Query() query: any, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
|
|
85
|
+
return this.service.findOne(+id, query, solidRequestContext);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
@Delete('/bulk')
|
|
89
|
-
async deleteMany(@Body() ids: number[]
|
|
90
|
-
return this.service.deleteMany(ids,solidRequestContext);
|
|
89
|
+
async deleteMany(@Body() ids: number[], @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
|
|
90
|
+
return this.service.deleteMany(ids, solidRequestContext);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
@ApiBearerAuth("jwt")
|
|
94
94
|
@Delete(':id')
|
|
95
|
-
async delete(@Param('id') id: number
|
|
95
|
+
async delete(@Param('id') id: number, @SolidRequestContextDecorator() solidRequestContext: SolidRequestContextDto) {
|
|
96
96
|
return this.service.delete(id, solidRequestContext);
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -920,7 +920,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
920
920
|
);
|
|
921
921
|
|
|
922
922
|
return { message: SUCCESS_MESSAGES.RECORD_RECOVERED, data: softDeletedRows };
|
|
923
|
-
} catch (error) {
|
|
923
|
+
} catch (error: any) {
|
|
924
924
|
if (error instanceof QueryFailedError) {
|
|
925
925
|
if ((error as any).code === '23505') {
|
|
926
926
|
throw new Error(ERROR_MESSAGES.CONFLICTING_RECORD_ON_UNARCHIVE);
|
|
@@ -966,7 +966,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
966
966
|
);
|
|
967
967
|
|
|
968
968
|
return { message: SUCCESS_MESSAGES.SELECTED_RECORDS_RECOVERED, recoveredIds: ids };
|
|
969
|
-
} catch (error) {
|
|
969
|
+
} catch (error: any) {
|
|
970
970
|
if (error instanceof QueryFailedError) {
|
|
971
971
|
if ((error as any).code === "23505") {
|
|
972
972
|
throw new Error(ERROR_MESSAGES.CONFLICTING_RECORD_ON_UNARCHIVE);
|
|
@@ -745,6 +745,8 @@ export class FieldMetadataService implements OnApplicationBootstrap {
|
|
|
745
745
|
"type",
|
|
746
746
|
"ormType",
|
|
747
747
|
"isSystem",
|
|
748
|
+
"regexPattern",
|
|
749
|
+
"regexPatternNotMatchingErrorMsg",
|
|
748
750
|
"defaultValue",
|
|
749
751
|
"min",
|
|
750
752
|
"max",
|
|
@@ -772,8 +774,6 @@ export class FieldMetadataService implements OnApplicationBootstrap {
|
|
|
772
774
|
"regexPattern",
|
|
773
775
|
"regexPatternNotMatchingErrorMsg",
|
|
774
776
|
"defaultValue",
|
|
775
|
-
"min",
|
|
776
|
-
"max",
|
|
777
777
|
"required",
|
|
778
778
|
"unique",
|
|
779
779
|
"index",
|
|
@@ -16,9 +16,9 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
16
16
|
protected readonly mqMessageQueueService: MqMessageQueueService,
|
|
17
17
|
protected readonly poller: PollerService,
|
|
18
18
|
) {
|
|
19
|
-
this.serviceRole = process.env.QUEUES_SERVICE_ROLE;
|
|
20
|
-
if (!
|
|
21
|
-
this.logger.debug('
|
|
19
|
+
this.serviceRole = process.env.QUEUES_SERVICE_ROLE || 'both';
|
|
20
|
+
if (!process.env.QUEUES_SERVICE_ROLE) {
|
|
21
|
+
this.logger.debug('QUEUES_SERVICE_ROLE is not defined. Defaulting DatabaseSubscriber service role to "both".');
|
|
22
22
|
}
|
|
23
23
|
// this.logger.debug(`DatabaseSubscriber instance created with options: ${JSON.stringify(this.options())}`);
|
|
24
24
|
}
|
|
@@ -60,7 +60,7 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
60
60
|
|
|
61
61
|
await this.processMessage(message);
|
|
62
62
|
}
|
|
63
|
-
catch (error) {
|
|
63
|
+
catch (error: any) {
|
|
64
64
|
this.logger.error(`Error processing message: ${error.message}`);
|
|
65
65
|
|
|
66
66
|
// if an error occurs then if retryCount is set we start retrying.
|
|
@@ -152,7 +152,7 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
152
152
|
private async retryMessage(message: QueueMessage<T>) {
|
|
153
153
|
try {
|
|
154
154
|
await this.processMessage(message);
|
|
155
|
-
} catch (error) {
|
|
155
|
+
} catch (error: any) {
|
|
156
156
|
if (message.currentRetry < message.retryCount) {
|
|
157
157
|
await this.updateStatusInDatabase('retrying', message);
|
|
158
158
|
|
|
@@ -203,7 +203,7 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
203
203
|
this.logger.debug(`Message status updated to ${stage} for messageId: ${mqMessage.id}`);
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
|
-
catch (error) {
|
|
206
|
+
catch (error: any) {
|
|
207
207
|
this.logger.error(error.message, error.stack);
|
|
208
208
|
}
|
|
209
209
|
}
|
|
@@ -31,12 +31,12 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
31
31
|
|
|
32
32
|
constructor(protected readonly mqMessageService: MqMessageService, protected readonly mqMessageQueueService: MqMessageQueueService) {
|
|
33
33
|
this.url = process.env.QUEUES_RABBIT_MQ_URL;
|
|
34
|
-
this.serviceRole = process.env.QUEUES_SERVICE_ROLE;
|
|
34
|
+
this.serviceRole = process.env.QUEUES_SERVICE_ROLE || 'both';
|
|
35
35
|
if (!this.url) {
|
|
36
36
|
this.logger.debug('RabbitMqPublisher url is not defined in the environment variables');
|
|
37
37
|
}
|
|
38
|
-
if (!
|
|
39
|
-
this.logger.debug('
|
|
38
|
+
if (!process.env.QUEUES_SERVICE_ROLE) {
|
|
39
|
+
this.logger.debug('QUEUES_SERVICE_ROLE is not defined. Defaulting RabbitMqSubscriber service role to "both".');
|
|
40
40
|
}
|
|
41
41
|
// this.logger.debug(`RabbitMqSubscriber instance created with options: ${JSON.stringify(this.options())} and url: ${this.url}`);
|
|
42
42
|
}
|
|
@@ -85,7 +85,7 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
85
85
|
async onModuleInit(): Promise<void> {
|
|
86
86
|
// Not using SettingService here as that will necessitate all implementors of RabbitMqSubscriber to also inject SettingService which is not ideal.
|
|
87
87
|
// Instead we directly read the environment variables here.
|
|
88
|
-
const defaultBroker = process.env.QUEUES_DEFAULT_BROKER || '
|
|
88
|
+
const defaultBroker = process.env.QUEUES_DEFAULT_BROKER || 'database';
|
|
89
89
|
const solidCliRunning = process.env.SOLID_CLI_RUNNING || "false";
|
|
90
90
|
const queueNameRegex = (process.env.QUEUES_QUEUE_NAME_REGEX_TO_ENABLE || '').trim();
|
|
91
91
|
const roleAllowed = ['both', 'subscriber'].includes(this.serviceRole);
|
|
@@ -407,7 +407,7 @@ export abstract class RabbitMqSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
407
407
|
await this.mqMessageService.repo.update(mqMessage.id, updatedFields);
|
|
408
408
|
}
|
|
409
409
|
}
|
|
410
|
-
catch (error) {
|
|
410
|
+
catch (error: any) {
|
|
411
411
|
this.logger.error(error.message, error.stack);
|
|
412
412
|
}
|
|
413
413
|
|
|
@@ -17,7 +17,10 @@ export abstract class RedisSubscriber<T> implements OnModuleInit, OnModuleDestro
|
|
|
17
17
|
protected readonly mqMessageService: MqMessageService,
|
|
18
18
|
protected readonly mqMessageQueueService: MqMessageQueueService,
|
|
19
19
|
) {
|
|
20
|
-
this.serviceRole = process.env.QUEUES_SERVICE_ROLE;
|
|
20
|
+
this.serviceRole = process.env.QUEUES_SERVICE_ROLE || 'both';
|
|
21
|
+
if (!process.env.QUEUES_SERVICE_ROLE) {
|
|
22
|
+
this.logger.debug('QUEUES_SERVICE_ROLE is not defined. Defaulting RedisSubscriber service role to "both".');
|
|
23
|
+
}
|
|
21
24
|
if (!process.env.QUEUES_REDIS_URL) {
|
|
22
25
|
this.logger.debug('RedisSubscriber: QUEUES_REDIS_URL is not defined in the environment variables');
|
|
23
26
|
}
|
|
@@ -201,7 +204,7 @@ export abstract class RedisSubscriber<T> implements OnModuleInit, OnModuleDestro
|
|
|
201
204
|
if (stage === 'failed') updatedFields['error'] = error;
|
|
202
205
|
await this.mqMessageService.repo.update(mqMessage.id, updatedFields);
|
|
203
206
|
}
|
|
204
|
-
} catch (err) {
|
|
207
|
+
} catch (err: any) {
|
|
205
208
|
this.logger.error(err.message, err.stack);
|
|
206
209
|
}
|
|
207
210
|
}
|
package/src/solid-core.module.ts
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { ConsoleReporter } from "./console-reporter";
|
|
2
|
+
import type { OpStep, ScenarioSpec } from "../contracts/testing-metadata.types";
|
|
3
|
+
import type { StepPhase } from "../contracts/runtime-context.types";
|
|
4
|
+
|
|
5
|
+
interface TestStepResult {
|
|
6
|
+
phase: string;
|
|
7
|
+
operation: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
ok: boolean;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface TestScenarioResult {
|
|
15
|
+
id: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
ok: boolean;
|
|
18
|
+
durationMs: number;
|
|
19
|
+
error?: string;
|
|
20
|
+
steps: TestStepResult[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TestRunPayload {
|
|
24
|
+
runName: string;
|
|
25
|
+
startedAt: string;
|
|
26
|
+
completedAt: string;
|
|
27
|
+
durationMs: number;
|
|
28
|
+
exitCode: number;
|
|
29
|
+
total: number;
|
|
30
|
+
passed: number;
|
|
31
|
+
failed: number;
|
|
32
|
+
scenarios: TestScenarioResult[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatError(err: unknown): string {
|
|
36
|
+
if (!err) return "";
|
|
37
|
+
if (err instanceof Error) return err.stack || err.message;
|
|
38
|
+
return String(err);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class WebhookReporter extends ConsoleReporter {
|
|
42
|
+
private readonly startedAt = new Date();
|
|
43
|
+
private readonly accumulated: TestScenarioResult[] = [];
|
|
44
|
+
private currentSteps: TestStepResult[] = [];
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
private readonly webhookUrl: string,
|
|
48
|
+
private readonly runName: string,
|
|
49
|
+
) {
|
|
50
|
+
super();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override onStepEnd(args: {
|
|
54
|
+
scenarioId: string;
|
|
55
|
+
phase: StepPhase;
|
|
56
|
+
step: OpStep;
|
|
57
|
+
ok: boolean;
|
|
58
|
+
error?: unknown;
|
|
59
|
+
durationMs: number;
|
|
60
|
+
}): void {
|
|
61
|
+
super.onStepEnd(args);
|
|
62
|
+
this.currentSteps.push({
|
|
63
|
+
phase: String(args.phase),
|
|
64
|
+
operation: args.step.op,
|
|
65
|
+
name: args.step.name,
|
|
66
|
+
ok: args.ok,
|
|
67
|
+
durationMs: args.durationMs,
|
|
68
|
+
error: args.error ? formatError(args.error) : undefined,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
override onScenarioEnd(
|
|
73
|
+
scenario: ScenarioSpec,
|
|
74
|
+
result: { ok: boolean; error?: unknown; durationMs: number },
|
|
75
|
+
): void {
|
|
76
|
+
super.onScenarioEnd(scenario, result);
|
|
77
|
+
this.accumulated.push({
|
|
78
|
+
id: scenario.id,
|
|
79
|
+
name: scenario.name,
|
|
80
|
+
ok: result.ok,
|
|
81
|
+
durationMs: result.durationMs,
|
|
82
|
+
error: result.error ? formatError(result.error) : undefined,
|
|
83
|
+
steps: [...this.currentSteps],
|
|
84
|
+
});
|
|
85
|
+
this.currentSteps = [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async flush(exitCode: number): Promise<void> {
|
|
89
|
+
const completedAt = new Date();
|
|
90
|
+
const payload: TestRunPayload = {
|
|
91
|
+
runName: this.runName,
|
|
92
|
+
startedAt: this.startedAt.toISOString(),
|
|
93
|
+
completedAt: completedAt.toISOString(),
|
|
94
|
+
durationMs: completedAt.getTime() - this.startedAt.getTime(),
|
|
95
|
+
exitCode,
|
|
96
|
+
total: this.accumulated.length,
|
|
97
|
+
passed: this.accumulated.filter((s) => s.ok).length,
|
|
98
|
+
failed: this.accumulated.filter((s) => !s.ok).length,
|
|
99
|
+
scenarios: this.accumulated,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const response = await fetch(this.webhookUrl, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { "Content-Type": "application/json" },
|
|
106
|
+
body: JSON.stringify(payload),
|
|
107
|
+
signal: AbortSignal.timeout(10_000),
|
|
108
|
+
});
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
console.warn(`[WebhookReporter] Webhook returned ${response.status}`);
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.warn(`[WebhookReporter] Failed to deliver test results: ${err}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|