@sentzunhat/zacatl 0.0.5 → 0.0.7
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 +192 -111
- package/context.yaml +151 -0
- package/guidelines.yaml +74 -0
- package/mongodb.yaml +29 -0
- package/package.json +33 -2
- package/patterns.yaml +55 -0
- package/src/error/custom.ts +3 -2
- package/src/micro-service/architecture/application/entry-points/rest/common/handler.ts +1 -2
- package/src/micro-service/architecture/application/entry-points/rest/route-handlers/abstract.ts +16 -2
- package/src/micro-service/architecture/application/entry-points/rest/route-handlers/get-route-handler.ts +3 -5
- package/src/micro-service/architecture/application/entry-points/rest/route-handlers/post-route-handler.ts +4 -6
- package/src/micro-service/architecture/application/entry-points/rest/route-handlers/route-handler.ts +1 -2
- package/src/micro-service/architecture/infrastructure/repositories/abstract.ts +94 -18
- package/src/micro-service/architecture/platform/service/service.ts +4 -0
- package/start.md +34 -0
- package/test/unit/micro-service/architecture/application/application.test.ts +19 -2
- package/test/unit/micro-service/architecture/application/entry-points/rest/route-handlers/abstract.test.ts +30 -10
- package/test/unit/micro-service/architecture/application/entry-points/rest/route-handlers/post-route-handler.test.ts +14 -9
- package/test/unit/micro-service/architecture/infrastructure/repositories/abstract.test.ts +6 -9
- package/test/unit/micro-service/architecture/platform/service/service.test.ts +3 -3
- package/src/errors.ts +0 -72
package/patterns.yaml
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Zacatl Project: Coding Patterns and Architectural Practices
|
|
2
|
+
|
|
3
|
+
origin:
|
|
4
|
+
- coding_standards.md
|
|
5
|
+
- coding_standards.pdf
|
|
6
|
+
- README.md (patterns section)
|
|
7
|
+
|
|
8
|
+
patterns:
|
|
9
|
+
- hexagonal_architecture:
|
|
10
|
+
description: |
|
|
11
|
+
Structure the application to separate core business logic from external dependencies using Ports and Adapters. Core logic is in the Domain layer, with Application, Infrastructure, and Platform layers handling entry points, persistence, and orchestration.
|
|
12
|
+
source: coding_standards.pdf
|
|
13
|
+
- object_oriented_programming:
|
|
14
|
+
description: |
|
|
15
|
+
Use classes to encapsulate state and behavior, especially for providers, repositories, and server classes. Follows OOP principles for modularity and encapsulation.
|
|
16
|
+
source: coding_standards.pdf
|
|
17
|
+
- functional_programming:
|
|
18
|
+
description: |
|
|
19
|
+
Use pure functions and higher-order functions for stateless operations and utility logic. Prefer immutability and function composition where possible.
|
|
20
|
+
source: coding_standards.pdf
|
|
21
|
+
- dependency_injection:
|
|
22
|
+
description: |
|
|
23
|
+
Use tsyringe for DI. Register all services, repositories, and handlers in the DI container. Never instantiate dependencies directly; always resolve via DI. Promotes modularity, flexibility, and testability.
|
|
24
|
+
source: coding_standards.pdf
|
|
25
|
+
- modular_server_design:
|
|
26
|
+
description: |
|
|
27
|
+
Server classes (e.g., ModularServer, FastifyServer) accept dependencies (Fastify, providers, databases, etc.) via constructor injection. Route registration and DI setup are centralized.
|
|
28
|
+
source: coding_standards.pdf
|
|
29
|
+
- centralized_configuration:
|
|
30
|
+
description: |
|
|
31
|
+
Use a DI container to register and configure dependencies (providers, repositories, Fastify instances, etc.). Server initialization is performed by resolving the server from the DI container and starting it on a specified port.
|
|
32
|
+
source: coding_standards.pdf
|
|
33
|
+
- error_handling:
|
|
34
|
+
description: |
|
|
35
|
+
All custom errors extend CustomError. Use CustomError.handle(error) in middleware or route handlers to log/format errors. Always include relevant metadata and a clear message. Let errors bubble up to Fastify’s error handler unless custom handling is needed.
|
|
36
|
+
source: README.md
|
|
37
|
+
- validation:
|
|
38
|
+
description: |
|
|
39
|
+
Use zod schemas for all request/response validation. Place validation logic in the Application layer.
|
|
40
|
+
source: README.md
|
|
41
|
+
- testing:
|
|
42
|
+
description: |
|
|
43
|
+
Use vitest for all tests. Place tests in test/, mirroring src/ structure. Write unit tests for all logic. Mock all external dependencies (DB, services) in tests. Use descriptive test names and group related tests in files named after the module under test.
|
|
44
|
+
source: README.md
|
|
45
|
+
|
|
46
|
+
best_practices:
|
|
47
|
+
- clean_code:
|
|
48
|
+
description: Write clean, modular, and straightforward code with meaningful names and strong typing. Use the latest language features and avoid redundancy (DRY principle).
|
|
49
|
+
source: coding_standards.pdf
|
|
50
|
+
- security_and_ethics:
|
|
51
|
+
description: Validate all inputs, handle errors gracefully, and never expose sensitive data. Code should be secure, efficient, and ethical.
|
|
52
|
+
source: coding_standards.pdf
|
|
53
|
+
- minimal_comments:
|
|
54
|
+
description: Prefer clarity in code and tests over excessive comments. Use comments only when necessary for context.
|
|
55
|
+
source: coding_standards.pdf
|
package/src/error/custom.ts
CHANGED
|
@@ -14,7 +14,9 @@ export type HttpStatusCode =
|
|
|
14
14
|
| 502 // Bad Gateway
|
|
15
15
|
| 503; // Service Unavailable;
|
|
16
16
|
|
|
17
|
-
export type
|
|
17
|
+
export type StatusCodeString = "invalid";
|
|
18
|
+
|
|
19
|
+
export type ErrorCode = Optional<HttpStatusCode | StatusCodeString>;
|
|
18
20
|
|
|
19
21
|
export interface CustomErrorsArgs {
|
|
20
22
|
message: string;
|
|
@@ -42,7 +44,6 @@ export class CustomError extends Error {
|
|
|
42
44
|
|
|
43
45
|
constructor({ message, code, reason, metadata, error }: CustomErrorArgs) {
|
|
44
46
|
super(message);
|
|
45
|
-
|
|
46
47
|
this.id = uuidv4();
|
|
47
48
|
this.custom = true;
|
|
48
49
|
this.name = this.constructor.name;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { FastifyReply } from "fastify";
|
|
3
2
|
|
|
4
3
|
import { Request } from "./request";
|
|
5
4
|
|
|
6
5
|
export type Handler<
|
|
7
6
|
TBody,
|
|
8
|
-
TQuerystring =
|
|
7
|
+
TQuerystring = Record<string, string>,
|
|
9
8
|
TParams = void
|
|
10
9
|
> = (
|
|
11
10
|
request: Request<TBody, TQuerystring, TParams>,
|
package/src/micro-service/architecture/application/entry-points/rest/route-handlers/abstract.ts
CHANGED
|
@@ -5,6 +5,20 @@ import { HTTPMethods, FastifySchema, FastifyReply } from "fastify";
|
|
|
5
5
|
import { RouteHandler } from "./route-handler";
|
|
6
6
|
import { Request } from "../common/request";
|
|
7
7
|
|
|
8
|
+
export type RouteSchema<
|
|
9
|
+
TBody = void,
|
|
10
|
+
TQuerystring = Record<string, string>,
|
|
11
|
+
TParams = Record<string, string>,
|
|
12
|
+
THeaders = Record<string, string>,
|
|
13
|
+
TResponse = void
|
|
14
|
+
> = {
|
|
15
|
+
body?: z.ZodSchema<TBody>;
|
|
16
|
+
querystring?: z.ZodSchema<TQuerystring>;
|
|
17
|
+
params?: z.ZodSchema<TParams>;
|
|
18
|
+
headers?: z.ZodSchema<THeaders>;
|
|
19
|
+
response?: z.ZodSchema<TResponse>;
|
|
20
|
+
};
|
|
21
|
+
|
|
8
22
|
export type AbstractRouteHandlerConstructor = {
|
|
9
23
|
url: string;
|
|
10
24
|
method: HTTPMethods;
|
|
@@ -15,9 +29,9 @@ export type HandlerOutput<TResponse> = TResponse;
|
|
|
15
29
|
|
|
16
30
|
export abstract class AbstractRouteHandler<
|
|
17
31
|
TBody = void,
|
|
18
|
-
TQuerystring =
|
|
32
|
+
TQuerystring = Record<string, string>,
|
|
19
33
|
TResponse = void,
|
|
20
|
-
TParams =
|
|
34
|
+
TParams = Record<string, string>
|
|
21
35
|
> implements RouteHandler<TBody, TQuerystring, TParams>
|
|
22
36
|
{
|
|
23
37
|
public url: string;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { FastifySchema } from "fastify";
|
|
3
|
-
|
|
4
2
|
import { AbstractRouteHandler } from "./abstract";
|
|
5
3
|
|
|
6
4
|
export type GetRouteHandlerConstructor = {
|
|
@@ -9,9 +7,9 @@ export type GetRouteHandlerConstructor = {
|
|
|
9
7
|
};
|
|
10
8
|
|
|
11
9
|
export abstract class GetRouteHandler<
|
|
12
|
-
TBody =
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
TBody = void,
|
|
11
|
+
TQuerystring = Record<string, string>,
|
|
12
|
+
TResponse = void,
|
|
15
13
|
TParams = void
|
|
16
14
|
> extends AbstractRouteHandler<TBody, TQuerystring, TResponse, TParams> {
|
|
17
15
|
constructor(args: GetRouteHandlerConstructor) {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { FastifySchema } from "fastify";
|
|
3
|
-
|
|
4
2
|
import { AbstractRouteHandler } from "./abstract";
|
|
5
3
|
|
|
6
4
|
export type PostRouteHandlerConstructor = {
|
|
@@ -9,10 +7,10 @@ export type PostRouteHandlerConstructor = {
|
|
|
9
7
|
};
|
|
10
8
|
|
|
11
9
|
export abstract class PostRouteHandler<
|
|
12
|
-
TBody =
|
|
13
|
-
TResponse =
|
|
14
|
-
TQuerystring =
|
|
15
|
-
TParams =
|
|
10
|
+
TBody = unknown,
|
|
11
|
+
TResponse = unknown,
|
|
12
|
+
TQuerystring = Record<string, string>,
|
|
13
|
+
TParams = Record<string, string>
|
|
16
14
|
> extends AbstractRouteHandler<TBody, TQuerystring, TResponse, TParams> {
|
|
17
15
|
constructor(args: PostRouteHandlerConstructor) {
|
|
18
16
|
super({
|
package/src/micro-service/architecture/application/entry-points/rest/route-handlers/route-handler.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { FastifySchema, HTTPMethods } from "fastify";
|
|
3
2
|
|
|
4
3
|
import { Handler } from "../common/handler";
|
|
5
4
|
|
|
6
5
|
export type RouteHandler<
|
|
7
6
|
TBody = void,
|
|
8
|
-
TQuerystring =
|
|
7
|
+
TQuerystring = Record<string, string>,
|
|
9
8
|
TParams = void
|
|
10
9
|
> = {
|
|
11
10
|
url: string;
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import importedMongoose, {
|
|
2
2
|
connection,
|
|
3
|
+
IfAny,
|
|
3
4
|
Model,
|
|
4
5
|
Mongoose,
|
|
6
|
+
Document,
|
|
5
7
|
Schema,
|
|
8
|
+
Default__v,
|
|
9
|
+
Require_id,
|
|
10
|
+
ObjectId,
|
|
6
11
|
} from "mongoose";
|
|
7
12
|
import { v4 as uuidv4 } from "uuid";
|
|
8
13
|
import { container } from "tsyringe";
|
|
@@ -12,17 +17,46 @@ export type BaseRepositoryConfig<D> = {
|
|
|
12
17
|
schema: Schema<D>;
|
|
13
18
|
};
|
|
14
19
|
|
|
20
|
+
export type WithMongooseMeta<T> = Default__v<Require_id<T>>;
|
|
21
|
+
|
|
22
|
+
export type MongooseDocument<Db> = Document<
|
|
23
|
+
ObjectId,
|
|
24
|
+
{},
|
|
25
|
+
Db,
|
|
26
|
+
Record<string, string>
|
|
27
|
+
> &
|
|
28
|
+
WithMongooseMeta<Db>;
|
|
29
|
+
|
|
30
|
+
export type LeanWithMeta<T> = WithMongooseMeta<T> & {
|
|
31
|
+
id: string;
|
|
32
|
+
createdAt: Date;
|
|
33
|
+
updatedAt: Date;
|
|
34
|
+
};
|
|
35
|
+
|
|
15
36
|
export type LeanDocument<T> = T & {
|
|
16
37
|
id: string;
|
|
17
38
|
createdAt: Date;
|
|
18
39
|
updatedAt: Date;
|
|
19
40
|
};
|
|
20
41
|
|
|
42
|
+
export type MongooseDoc<Db> = IfAny<
|
|
43
|
+
MongooseDocument<Db>,
|
|
44
|
+
MongooseDocument<Db>,
|
|
45
|
+
MongooseDocument<Db>
|
|
46
|
+
>;
|
|
47
|
+
|
|
48
|
+
export type ToLeanInput<D, T> =
|
|
49
|
+
| MongooseDoc<D>
|
|
50
|
+
| LeanDocument<T>
|
|
51
|
+
| null
|
|
52
|
+
| undefined;
|
|
53
|
+
|
|
21
54
|
// D - Document, meant for Database representation
|
|
22
55
|
// T - Type, meant for TypeScript representation
|
|
23
56
|
export type Repository<D, T> = {
|
|
24
57
|
model: Model<D>;
|
|
25
58
|
|
|
59
|
+
toLean(input: ToLeanInput<D, T>): LeanDocument<T> | null;
|
|
26
60
|
findById(id: string): Promise<LeanDocument<T> | null>;
|
|
27
61
|
create(entity: D): Promise<LeanDocument<T>>;
|
|
28
62
|
update(id: string, update: Partial<D>): Promise<LeanDocument<T> | null>;
|
|
@@ -31,13 +65,15 @@ export type Repository<D, T> = {
|
|
|
31
65
|
|
|
32
66
|
export abstract class BaseRepository<D, T> implements Repository<D, T> {
|
|
33
67
|
public readonly model: Model<D>;
|
|
68
|
+
private readonly config: BaseRepositoryConfig<D>;
|
|
34
69
|
|
|
35
70
|
constructor(config: BaseRepositoryConfig<D>) {
|
|
71
|
+
this.config = config;
|
|
36
72
|
const mongoose = connection.db?.databaseName
|
|
37
73
|
? importedMongoose
|
|
38
74
|
: container.resolve<Mongoose>(Mongoose);
|
|
39
75
|
|
|
40
|
-
const { name, schema } = config;
|
|
76
|
+
const { name, schema } = this.config;
|
|
41
77
|
|
|
42
78
|
if (name) {
|
|
43
79
|
this.model = mongoose.model<D>(name, schema);
|
|
@@ -50,23 +86,71 @@ export abstract class BaseRepository<D, T> implements Repository<D, T> {
|
|
|
50
86
|
this.model.init();
|
|
51
87
|
}
|
|
52
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Ensures the returned document has the correct id, createdAt, and updatedAt fields.
|
|
91
|
+
* Accepts a Mongoose document, a plain object, or a result from .lean().
|
|
92
|
+
*/
|
|
93
|
+
public toLean(input: ToLeanInput<D, T>): LeanDocument<T> | null {
|
|
94
|
+
if (!input) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let base: LeanWithMeta<T>;
|
|
99
|
+
|
|
100
|
+
if (typeof (input as MongooseDoc<D>).toObject === "function") {
|
|
101
|
+
base = (input as MongooseDoc<D>).toObject<LeanDocument<T>>({
|
|
102
|
+
virtuals: true,
|
|
103
|
+
});
|
|
104
|
+
} else {
|
|
105
|
+
base = input as LeanWithMeta<T>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const result: LeanDocument<T> = {
|
|
109
|
+
...(base as T), // trust only known keys from T
|
|
110
|
+
id:
|
|
111
|
+
typeof base.id === "string"
|
|
112
|
+
? base.id
|
|
113
|
+
: typeof base._id === "string"
|
|
114
|
+
? base._id
|
|
115
|
+
: base._id !== undefined
|
|
116
|
+
? String(base._id)
|
|
117
|
+
: "",
|
|
118
|
+
createdAt:
|
|
119
|
+
base.createdAt instanceof Date
|
|
120
|
+
? base.createdAt
|
|
121
|
+
: base.createdAt
|
|
122
|
+
? new Date(base.createdAt as string | number)
|
|
123
|
+
: new Date(),
|
|
124
|
+
updatedAt:
|
|
125
|
+
base.updatedAt instanceof Date
|
|
126
|
+
? base.updatedAt
|
|
127
|
+
: base.updatedAt
|
|
128
|
+
? new Date(base.updatedAt as string | number)
|
|
129
|
+
: new Date(),
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
53
135
|
async findById(id: string): Promise<LeanDocument<T> | null> {
|
|
54
136
|
const entity = await this.model
|
|
55
137
|
.findById(id)
|
|
56
138
|
.lean<LeanDocument<T>>({ virtuals: true })
|
|
57
139
|
.exec();
|
|
58
140
|
|
|
59
|
-
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return entity;
|
|
141
|
+
return this.toLean(entity);
|
|
64
142
|
}
|
|
65
143
|
|
|
66
144
|
async create(entity: D): Promise<LeanDocument<T>> {
|
|
67
|
-
const
|
|
145
|
+
const document = await this.model.create<D>(entity);
|
|
68
146
|
|
|
69
|
-
|
|
147
|
+
const leanDocument = this.toLean(document as MongooseDoc<D>);
|
|
148
|
+
|
|
149
|
+
if (!leanDocument) {
|
|
150
|
+
throw new Error("failed to create document");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return leanDocument;
|
|
70
154
|
}
|
|
71
155
|
|
|
72
156
|
async update(
|
|
@@ -78,11 +162,7 @@ export abstract class BaseRepository<D, T> implements Repository<D, T> {
|
|
|
78
162
|
.lean<LeanDocument<T>>({ virtuals: true })
|
|
79
163
|
.exec();
|
|
80
164
|
|
|
81
|
-
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return entity;
|
|
165
|
+
return this.toLean(entity);
|
|
86
166
|
}
|
|
87
167
|
|
|
88
168
|
async delete(id: string): Promise<LeanDocument<T> | null> {
|
|
@@ -91,10 +171,6 @@ export abstract class BaseRepository<D, T> implements Repository<D, T> {
|
|
|
91
171
|
.lean<LeanDocument<T>>({ virtuals: true })
|
|
92
172
|
.exec();
|
|
93
173
|
|
|
94
|
-
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return entity;
|
|
174
|
+
return this.toLean(entity);
|
|
99
175
|
}
|
|
100
176
|
}
|
|
@@ -206,6 +206,10 @@ export class Service implements ServicePort {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
public async configureDatabases(): Promise<void> {
|
|
209
|
+
if (!this.databases || this.databases.length === 0) {
|
|
210
|
+
return; // No databases to configure
|
|
211
|
+
}
|
|
212
|
+
|
|
209
213
|
for (const database of this.databases) {
|
|
210
214
|
const strategy = strategiesForDatabaseVendor[database.vendor];
|
|
211
215
|
|
package/start.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Zacatl Project Onboarding & Usage
|
|
2
|
+
|
|
3
|
+
Welcome to Zacatl! This project uses structured YAML documentation for all context, architecture, coding standards, and best practices.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
- **Project context, architecture, and component summaries:**
|
|
8
|
+
See [`context.yaml`](./context.yaml)
|
|
9
|
+
- **Coding standards, naming conventions, and best practices:**
|
|
10
|
+
See [`guidelines.yaml`](./guidelines.yaml)
|
|
11
|
+
- **Design and usage patterns:**
|
|
12
|
+
See [`patterns.yaml`](./patterns.yaml)
|
|
13
|
+
- **MongoDB schema design guidelines:**
|
|
14
|
+
See [`mongodb.yaml`](./mongodb.yaml)
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
1. Install dependencies:
|
|
19
|
+
```zsh
|
|
20
|
+
npm install
|
|
21
|
+
```
|
|
22
|
+
2. Run tests:
|
|
23
|
+
```zsh
|
|
24
|
+
npm test
|
|
25
|
+
```
|
|
26
|
+
3. Explore the codebase, starting from `src/`.
|
|
27
|
+
|
|
28
|
+
## Contributing
|
|
29
|
+
|
|
30
|
+
- Follow the guidelines in `guidelines.yaml` and `patterns.yaml`.
|
|
31
|
+
- Update `context.yaml`, `guidelines.yaml`, `patterns.yaml`, or `mongodb.yaml` with any new patterns or conventions.
|
|
32
|
+
- Place all tests in the `test/` directory, mirroring the `src/` structure.
|
|
33
|
+
|
|
34
|
+
For any questions, refer to the YAML documentation or contact the maintainers.
|
|
@@ -16,7 +16,14 @@ class DummyHookHandler implements HookHandler {
|
|
|
16
16
|
async execute(_: FastifyRequest): Promise<void> {}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
import { FastifyReply } from "fastify";
|
|
20
|
+
|
|
21
|
+
class DummyRouteHandler extends AbstractRouteHandler<
|
|
22
|
+
void, // Body
|
|
23
|
+
Record<string, string>, // Querystring
|
|
24
|
+
void, // Params
|
|
25
|
+
void // Response
|
|
26
|
+
> {
|
|
20
27
|
constructor() {
|
|
21
28
|
super({
|
|
22
29
|
url: "/",
|
|
@@ -25,7 +32,17 @@ class DummyRouteHandler extends AbstractRouteHandler {
|
|
|
25
32
|
});
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
handler(
|
|
35
|
+
handler(
|
|
36
|
+
_: Request<
|
|
37
|
+
void, // Body
|
|
38
|
+
Record<string, string>, // Querystring
|
|
39
|
+
void // Params
|
|
40
|
+
>,
|
|
41
|
+
__: FastifyReply
|
|
42
|
+
): void | Promise<void> {
|
|
43
|
+
// Dummy implementation
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
29
46
|
}
|
|
30
47
|
|
|
31
48
|
const fakeConfig: ConfigApplication = {
|
|
@@ -10,9 +10,29 @@ import {
|
|
|
10
10
|
Request,
|
|
11
11
|
} from "../../../../../../../../src/micro-service/architecture/application";
|
|
12
12
|
|
|
13
|
-
class TestRouteHandler extends AbstractRouteHandler<
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
class TestRouteHandler extends AbstractRouteHandler<
|
|
14
|
+
void, // Body
|
|
15
|
+
Record<string, string>, // Querystring
|
|
16
|
+
void, // Params
|
|
17
|
+
void // Response
|
|
18
|
+
> {
|
|
19
|
+
constructor() {
|
|
20
|
+
super({
|
|
21
|
+
url: "/",
|
|
22
|
+
schema: {},
|
|
23
|
+
method: "GET",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
handler(
|
|
28
|
+
_: Request<
|
|
29
|
+
void, // Body
|
|
30
|
+
Record<string, string>, // Querystring
|
|
31
|
+
void // Params
|
|
32
|
+
>
|
|
33
|
+
): void | Promise<void> {
|
|
34
|
+
// Dummy implementation
|
|
35
|
+
return;
|
|
16
36
|
}
|
|
17
37
|
}
|
|
18
38
|
|
|
@@ -20,15 +40,15 @@ describe("AbstractRouteHandler", () => {
|
|
|
20
40
|
it("executes the handler and sends the proper response", async () => {
|
|
21
41
|
vi.spyOn(i18n, "__").mockReturnValue("Default success");
|
|
22
42
|
|
|
23
|
-
const fakeRequest = createFakeFastifyRequest() as Request<
|
|
43
|
+
const fakeRequest = createFakeFastifyRequest() as Request<
|
|
44
|
+
void, // Body
|
|
45
|
+
Record<string, string>, // Querystring
|
|
46
|
+
void // Params
|
|
47
|
+
>;
|
|
24
48
|
|
|
25
49
|
const fakeReply: any = createFakeFastifyReply();
|
|
26
50
|
|
|
27
|
-
const testHandler = new TestRouteHandler(
|
|
28
|
-
url: "/test",
|
|
29
|
-
method: "GET",
|
|
30
|
-
schema: {},
|
|
31
|
-
});
|
|
51
|
+
const testHandler = new TestRouteHandler();
|
|
32
52
|
|
|
33
53
|
await testHandler.execute(fakeRequest, fakeReply);
|
|
34
54
|
|
|
@@ -36,7 +56,7 @@ describe("AbstractRouteHandler", () => {
|
|
|
36
56
|
expect(fakeReply.send).toHaveBeenCalledWith({
|
|
37
57
|
ok: true,
|
|
38
58
|
message: "Default success",
|
|
39
|
-
data:
|
|
59
|
+
data: undefined,
|
|
40
60
|
});
|
|
41
61
|
});
|
|
42
62
|
});
|
|
@@ -10,9 +10,17 @@ import {
|
|
|
10
10
|
createFakeFastifyRequest,
|
|
11
11
|
} from "../../../../../../helpers/common/common";
|
|
12
12
|
|
|
13
|
-
class TestPostRouteHandler extends PostRouteHandler<
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
class TestPostRouteHandler extends PostRouteHandler<{}, {}, {}, {}> {
|
|
14
|
+
constructor() {
|
|
15
|
+
super({
|
|
16
|
+
url: "/post-test",
|
|
17
|
+
schema: {}, // Use an empty schema for test purposes.
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
handler(_: Request<{}, {}, {}>): {} | Promise<{}> {
|
|
22
|
+
// Dummy implementation
|
|
23
|
+
return {};
|
|
16
24
|
}
|
|
17
25
|
}
|
|
18
26
|
|
|
@@ -20,12 +28,9 @@ describe("PostRouteHandler", () => {
|
|
|
20
28
|
it("executes POST handler and sends proper response", async () => {
|
|
21
29
|
vi.spyOn(i18n, "__").mockReturnValue("Default success POST");
|
|
22
30
|
|
|
23
|
-
const testHandler = new TestPostRouteHandler(
|
|
24
|
-
url: "/post-test",
|
|
25
|
-
schema: {}, // Use an empty schema for test purposes.
|
|
26
|
-
});
|
|
31
|
+
const testHandler = new TestPostRouteHandler();
|
|
27
32
|
|
|
28
|
-
const fakeRequest = createFakeFastifyRequest() as Request<
|
|
33
|
+
const fakeRequest = createFakeFastifyRequest() as Request<{}, {}, {}>;
|
|
29
34
|
const fakeReply = createFakeFastifyReply();
|
|
30
35
|
|
|
31
36
|
await testHandler.execute(fakeRequest, fakeReply);
|
|
@@ -34,7 +39,7 @@ describe("PostRouteHandler", () => {
|
|
|
34
39
|
expect(fakeReply.send).toHaveBeenCalledWith({
|
|
35
40
|
ok: true,
|
|
36
41
|
message: "Default success POST",
|
|
37
|
-
data:
|
|
42
|
+
data: {},
|
|
38
43
|
});
|
|
39
44
|
});
|
|
40
45
|
});
|
|
@@ -13,7 +13,7 @@ const schemaUserTest = new Schema<UserTest>({
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
@singleton()
|
|
16
|
-
class UserRepository extends BaseRepository<UserTest> {
|
|
16
|
+
class UserRepository extends BaseRepository<UserTest, UserTest> {
|
|
17
17
|
constructor() {
|
|
18
18
|
super({ name: "User", schema: schemaUserTest });
|
|
19
19
|
}
|
|
@@ -46,13 +46,11 @@ describe("BaseRepository", () => {
|
|
|
46
46
|
|
|
47
47
|
it("should call findById() and return the user document", async () => {
|
|
48
48
|
const user = await repository.create({ name: "Alice" });
|
|
49
|
-
|
|
50
49
|
const spyFunction = vi.spyOn(repository.model, "findById");
|
|
51
|
-
|
|
52
50
|
const result = await repository.findById(user.id);
|
|
53
|
-
|
|
54
51
|
expect(spyFunction).toHaveBeenNthCalledWith(1, user.id);
|
|
55
|
-
expect(result).
|
|
52
|
+
expect(result).toMatchObject({ name: user.name });
|
|
53
|
+
expect(result?.id).toBe(user.id);
|
|
56
54
|
});
|
|
57
55
|
|
|
58
56
|
it("should call update() and return the updated user document", async () => {
|
|
@@ -72,12 +70,11 @@ describe("BaseRepository", () => {
|
|
|
72
70
|
|
|
73
71
|
it("should call delete() and return the deleted user document", async () => {
|
|
74
72
|
const user = await repository.create({ name: "Alice" });
|
|
75
|
-
|
|
76
73
|
const spyFunction = vi.spyOn(repository.model, "findByIdAndDelete");
|
|
77
|
-
|
|
78
74
|
const result = await repository.delete(user.id);
|
|
79
|
-
|
|
80
75
|
expect(spyFunction).toHaveBeenNthCalledWith(1, user.id);
|
|
81
|
-
|
|
76
|
+
// Patch: allow for id only (ignore _id)
|
|
77
|
+
expect(result).toMatchObject({ name: user.name });
|
|
78
|
+
expect(result?.id).toBe(user.id);
|
|
82
79
|
});
|
|
83
80
|
});
|
|
@@ -146,6 +146,8 @@ describe("Service", () => {
|
|
|
146
146
|
const port = 3000;
|
|
147
147
|
const service = new Service(config);
|
|
148
148
|
|
|
149
|
+
await service.configureDatabases();
|
|
150
|
+
|
|
149
151
|
await service.start({ port });
|
|
150
152
|
|
|
151
153
|
expect(
|
|
@@ -184,15 +186,13 @@ describe("Service", () => {
|
|
|
184
186
|
});
|
|
185
187
|
|
|
186
188
|
it("should throw an error if database configuration fails", async () => {
|
|
187
|
-
const port = 3000;
|
|
188
|
-
|
|
189
189
|
strategiesForDatabaseVendor[DatabaseVendor.MONGOOSE] = originalDbStrategy;
|
|
190
190
|
|
|
191
191
|
fakeMongoose.connect.mockRejectedValue(new Error("db connect failed"));
|
|
192
192
|
|
|
193
193
|
const service = new Service(config);
|
|
194
194
|
|
|
195
|
-
await expect(service.
|
|
195
|
+
await expect(service.configureDatabases()).rejects.toThrow(
|
|
196
196
|
"failed to configure database for service"
|
|
197
197
|
);
|
|
198
198
|
});
|