fragment-ts 1.0.16 → 1.0.18
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/SETUP.md +570 -0
- package/changes/1.md +420 -0
- package/dist/cli/commands/init.command.js +4 -4
- package/dist/cli/commands/init.command.js.map +1 -1
- package/dist/cli/commands/test.command.d.ts +6 -0
- package/dist/cli/commands/test.command.d.ts.map +1 -0
- package/dist/cli/commands/test.command.js +311 -0
- package/dist/cli/commands/test.command.js.map +1 -0
- package/dist/cli/index.js +6 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/core/container/di-container.d.ts +5 -0
- package/dist/core/container/di-container.d.ts.map +1 -1
- package/dist/core/container/di-container.js +121 -21
- package/dist/core/container/di-container.js.map +1 -1
- package/dist/core/decorators/controller.decorator.js +5 -5
- package/dist/core/decorators/injection.decorators.d.ts +44 -1
- package/dist/core/decorators/injection.decorators.d.ts.map +1 -1
- package/dist/core/decorators/injection.decorators.js +92 -1
- package/dist/core/decorators/injection.decorators.js.map +1 -1
- package/dist/core/metadata/metadata-keys.d.ts +29 -17
- package/dist/core/metadata/metadata-keys.d.ts.map +1 -1
- package/dist/core/metadata/metadata-keys.js +35 -17
- package/dist/core/metadata/metadata-keys.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/init.command.ts +4 -4
- package/src/cli/commands/test.command.ts +289 -0
- package/src/cli/index.ts +16 -11
- package/src/core/container/di-container.ts +166 -31
- package/src/core/decorators/DECORATOR_USAGE.md +326 -0
- package/src/core/decorators/controller.decorator.ts +9 -9
- package/src/core/decorators/injection.decorators.ts +129 -5
- package/src/core/metadata/metadata-keys.ts +44 -18
- package/src/index.ts +1 -0
- package/src/testing/TEST.md +321 -0
|
@@ -1,26 +1,150 @@
|
|
|
1
|
-
import { METADATA_KEYS } from
|
|
1
|
+
import { METADATA_KEYS } from "../metadata/metadata-keys";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @Autowired - Automatically inject dependencies by type
|
|
5
|
+
* Usage: @Autowired() private myService: MyService;
|
|
6
|
+
*/
|
|
3
7
|
export function Autowired(): PropertyDecorator {
|
|
4
8
|
return (target: any, propertyKey: string | symbol) => {
|
|
5
|
-
|
|
9
|
+
// Get the design type from TypeScript metadata
|
|
10
|
+
const type = Reflect.getMetadata("design:type", target, propertyKey);
|
|
11
|
+
|
|
12
|
+
if (!type || type === Object) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Cannot use @Autowired on property "${String(propertyKey)}" in ${target.constructor.name}. ` +
|
|
15
|
+
`Make sure TypeScript emitDecoratorMetadata is enabled and the type is explicitly declared.`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Store the type in metadata so DI container can resolve it
|
|
6
20
|
Reflect.defineMetadata(METADATA_KEYS.AUTOWIRED, type, target, propertyKey);
|
|
7
21
|
};
|
|
8
22
|
}
|
|
9
23
|
|
|
10
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @Inject - Inject dependency by token (string or class)
|
|
26
|
+
* Usage: @Inject('MyService') private service: MyService;
|
|
27
|
+
* or: @Inject(MyService) private service: MyService;
|
|
28
|
+
*/
|
|
29
|
+
export function Inject(token: string | Function): PropertyDecorator {
|
|
11
30
|
return (target: any, propertyKey: string | symbol) => {
|
|
12
31
|
Reflect.defineMetadata(METADATA_KEYS.INJECT, token, target, propertyKey);
|
|
13
32
|
};
|
|
14
33
|
}
|
|
15
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @InjectRepository - Inject TypeORM repository for an entity
|
|
37
|
+
* Usage: @InjectRepository(User) private userRepo: Repository<User>;
|
|
38
|
+
*/
|
|
39
|
+
export function InjectRepository(entity: Function): PropertyDecorator {
|
|
40
|
+
return (target: any, propertyKey: string | symbol) => {
|
|
41
|
+
Reflect.defineMetadata(
|
|
42
|
+
METADATA_KEYS.INJECT_REPOSITORY,
|
|
43
|
+
entity,
|
|
44
|
+
target,
|
|
45
|
+
propertyKey,
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @Qualifier - Specify which bean to inject when multiple exist
|
|
52
|
+
* Usage: @Qualifier('primary') @Autowired() private service: MyService;
|
|
53
|
+
*/
|
|
16
54
|
export function Qualifier(name: string): PropertyDecorator {
|
|
17
55
|
return (target: any, propertyKey: string | symbol) => {
|
|
18
56
|
Reflect.defineMetadata(METADATA_KEYS.QUALIFIER, name, target, propertyKey);
|
|
19
57
|
};
|
|
20
58
|
}
|
|
21
59
|
|
|
60
|
+
/**
|
|
61
|
+
* @Value - Inject configuration value from environment or config
|
|
62
|
+
* Usage: @Value('${PORT}') private port: number;
|
|
63
|
+
* or: @Value('${DB_HOST:localhost}') private host: string;
|
|
64
|
+
*/
|
|
22
65
|
export function Value(expression: string): PropertyDecorator {
|
|
23
66
|
return (target: any, propertyKey: string | symbol) => {
|
|
24
|
-
Reflect.defineMetadata(
|
|
67
|
+
Reflect.defineMetadata(
|
|
68
|
+
METADATA_KEYS.VALUE,
|
|
69
|
+
expression,
|
|
70
|
+
target,
|
|
71
|
+
propertyKey,
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @Optional - Mark dependency as optional (won't throw if not found)
|
|
78
|
+
* Usage: @Optional() @Autowired() private service?: MyService;
|
|
79
|
+
*/
|
|
80
|
+
export function Optional(): PropertyDecorator {
|
|
81
|
+
return (target: any, propertyKey: string | symbol) => {
|
|
82
|
+
Reflect.defineMetadata(METADATA_KEYS.OPTIONAL, true, target, propertyKey);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @Lazy - Lazy load dependency (create on first access)
|
|
88
|
+
* Usage: @Lazy() @Autowired() private service: MyService;
|
|
89
|
+
*/
|
|
90
|
+
export function Lazy(): PropertyDecorator {
|
|
91
|
+
return (target: any, propertyKey: string | symbol) => {
|
|
92
|
+
Reflect.defineMetadata(METADATA_KEYS.LAZY, true, target, propertyKey);
|
|
93
|
+
|
|
94
|
+
const type = Reflect.getMetadata("design:type", target, propertyKey);
|
|
95
|
+
|
|
96
|
+
// Create a getter that resolves on first access
|
|
97
|
+
let cached: any = null;
|
|
98
|
+
let resolved = false;
|
|
99
|
+
|
|
100
|
+
Object.defineProperty(target, propertyKey, {
|
|
101
|
+
get() {
|
|
102
|
+
if (!resolved) {
|
|
103
|
+
const { DIContainer } = require("../container/di-container");
|
|
104
|
+
const container = DIContainer.getInstance();
|
|
105
|
+
cached = container.resolve(type);
|
|
106
|
+
resolved = true;
|
|
107
|
+
}
|
|
108
|
+
return cached;
|
|
109
|
+
},
|
|
110
|
+
enumerable: true,
|
|
111
|
+
configurable: true,
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @PostConstruct - Method called after dependency injection is complete
|
|
118
|
+
* Usage: @PostConstruct() init() { ... }
|
|
119
|
+
*/
|
|
120
|
+
export function PostConstruct(): MethodDecorator {
|
|
121
|
+
return (
|
|
122
|
+
target: any,
|
|
123
|
+
propertyKey: string | symbol,
|
|
124
|
+
descriptor: PropertyDescriptor,
|
|
125
|
+
) => {
|
|
126
|
+
Reflect.defineMetadata(
|
|
127
|
+
METADATA_KEYS.POST_CONSTRUCT,
|
|
128
|
+
propertyKey,
|
|
129
|
+
target.constructor,
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @PreDestroy - Method called before bean is destroyed
|
|
136
|
+
* Usage: @PreDestroy() cleanup() { ... }
|
|
137
|
+
*/
|
|
138
|
+
export function PreDestroy(): MethodDecorator {
|
|
139
|
+
return (
|
|
140
|
+
target: any,
|
|
141
|
+
propertyKey: string | symbol,
|
|
142
|
+
descriptor: PropertyDescriptor,
|
|
143
|
+
) => {
|
|
144
|
+
Reflect.defineMetadata(
|
|
145
|
+
METADATA_KEYS.PRE_DESTROY,
|
|
146
|
+
propertyKey,
|
|
147
|
+
target.constructor,
|
|
148
|
+
);
|
|
25
149
|
};
|
|
26
|
-
}
|
|
150
|
+
}
|
|
@@ -1,19 +1,45 @@
|
|
|
1
1
|
export const METADATA_KEYS = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
CONTROLLER:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
// Class decorators
|
|
3
|
+
APPLICATION: "fragment:application",
|
|
4
|
+
INJECTABLE: "fragment:injectable",
|
|
5
|
+
CONTROLLER: "fragment:controller",
|
|
6
|
+
SERVICE: "fragment:service",
|
|
7
|
+
REPOSITORY: "fragment:repository",
|
|
8
|
+
AUTO_CONFIGURATION: "fragment:auto-configuration",
|
|
9
|
+
SCOPE: "fragment:scope",
|
|
10
|
+
|
|
11
|
+
// Property decorators
|
|
12
|
+
AUTOWIRED: "fragment:autowired",
|
|
13
|
+
INJECT: "fragment:inject",
|
|
14
|
+
INJECT_REPOSITORY: "fragment:inject-repository",
|
|
15
|
+
QUALIFIER: "fragment:qualifier",
|
|
16
|
+
VALUE: "fragment:value",
|
|
17
|
+
OPTIONAL: "fragment:optional",
|
|
18
|
+
LAZY: "fragment:lazy",
|
|
19
|
+
|
|
20
|
+
// Method decorators
|
|
21
|
+
HTTP_METHOD: "fragment:http-method",
|
|
22
|
+
ROUTE_PATH: "fragment:route-path",
|
|
23
|
+
POST_CONSTRUCT: "fragment:post-construct",
|
|
24
|
+
PRE_DESTROY: "fragment:pre-destroy",
|
|
25
|
+
|
|
26
|
+
// Parameter decorators
|
|
27
|
+
PARAM_METADATA: "fragment:param-metadata",
|
|
28
|
+
|
|
29
|
+
// Conditional decorators
|
|
30
|
+
CONDITIONAL_ON_CLASS: "fragment:conditional-on-class",
|
|
31
|
+
CONDITIONAL_ON_MISSING_BEAN: "fragment:conditional-on-missing-bean",
|
|
32
|
+
CONDITIONAL_ON_PROPERTY: "fragment:conditional-on-property",
|
|
33
|
+
CONDITIONAL_ON_BEAN: "fragment:conditional-on-bean",
|
|
34
|
+
|
|
35
|
+
// Middleware & Guards
|
|
36
|
+
USE_GUARDS: "fragment:use-guards",
|
|
37
|
+
USE_INTERCEPTORS: "fragment:use-interceptors",
|
|
38
|
+
USE_FILTERS: "fragment:use-filters",
|
|
39
|
+
|
|
40
|
+
// Validation
|
|
41
|
+
VALIDATE: "fragment:validate",
|
|
42
|
+
TRANSFORM: "fragment:transform",
|
|
43
|
+
} as const;
|
|
44
|
+
|
|
45
|
+
export type MetadataKey = (typeof METADATA_KEYS)[keyof typeof METADATA_KEYS];
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// user.service.spec.ts
|
|
3
|
+
// Example test file for UserService
|
|
4
|
+
// ============================================
|
|
5
|
+
|
|
6
|
+
import 'reflect-metadata';
|
|
7
|
+
import { UserService } from './user.service';
|
|
8
|
+
import { UserRepository } from './user.repository';
|
|
9
|
+
import { EmailService } from './email.service';
|
|
10
|
+
import { DIContainer } from 'fragment-ts';
|
|
11
|
+
|
|
12
|
+
describe('UserService', () => {
|
|
13
|
+
let userService: UserService;
|
|
14
|
+
let userRepository: UserRepository;
|
|
15
|
+
let emailService: EmailService;
|
|
16
|
+
let container: DIContainer;
|
|
17
|
+
|
|
18
|
+
// Setup before each test
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Create a fresh DI container for each test
|
|
21
|
+
container = DIContainer.getInstance();
|
|
22
|
+
container.reset();
|
|
23
|
+
|
|
24
|
+
// Mock dependencies
|
|
25
|
+
userRepository = {
|
|
26
|
+
findAll: async () => [],
|
|
27
|
+
findById: async (id: number) => null,
|
|
28
|
+
findByEmail: async (email: string) => null,
|
|
29
|
+
create: async (data: any) => ({ id: 1, ...data }),
|
|
30
|
+
update: async (id: number, data: any) => null,
|
|
31
|
+
delete: async (id: number) => true,
|
|
32
|
+
} as any;
|
|
33
|
+
|
|
34
|
+
emailService = {
|
|
35
|
+
sendWelcomeEmail: async (email: string, name: string) => {},
|
|
36
|
+
sendPasswordReset: async (email: string, token: string) => {},
|
|
37
|
+
} as any;
|
|
38
|
+
|
|
39
|
+
// Register mocks in container
|
|
40
|
+
container.register(UserRepository, userRepository);
|
|
41
|
+
container.register(EmailService, emailService);
|
|
42
|
+
|
|
43
|
+
// Create service instance (will auto-inject mocks)
|
|
44
|
+
userService = container.resolve(UserService);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should get all users', async () => {
|
|
48
|
+
const mockUsers = [
|
|
49
|
+
{ id: 1, name: 'John', email: 'john@example.com', isActive: true },
|
|
50
|
+
{ id: 2, name: 'Jane', email: 'jane@example.com', isActive: true },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
userRepository.findAll = async () => mockUsers;
|
|
54
|
+
|
|
55
|
+
const users = await userService.getAllUsers();
|
|
56
|
+
|
|
57
|
+
expect(users).toEqual(mockUsers);
|
|
58
|
+
expect(users).toHaveLength(2);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should get user by id', async () => {
|
|
62
|
+
const mockUser = {
|
|
63
|
+
id: 1,
|
|
64
|
+
name: 'John',
|
|
65
|
+
email: 'john@example.com',
|
|
66
|
+
isActive: true,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
userRepository.findById = async (id: number) =>
|
|
70
|
+
id === 1 ? mockUser : null;
|
|
71
|
+
|
|
72
|
+
const user = await userService.getUserById(1);
|
|
73
|
+
|
|
74
|
+
expect(user).toEqual(mockUser);
|
|
75
|
+
expect(user?.name).toBe('John');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should create user and send welcome email', async () => {
|
|
79
|
+
const newUser = {
|
|
80
|
+
name: 'John',
|
|
81
|
+
email: 'john@example.com',
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
let emailSent = false;
|
|
85
|
+
emailService.sendWelcomeEmail = async () => {
|
|
86
|
+
emailSent = true;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const user = await userService.createUser(newUser);
|
|
90
|
+
|
|
91
|
+
expect(user.name).toBe('John');
|
|
92
|
+
expect(user.email).toBe('john@example.com');
|
|
93
|
+
expect(emailSent).toBeTruthy();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should throw error when creating duplicate email', async () => {
|
|
97
|
+
const existingUser = {
|
|
98
|
+
id: 1,
|
|
99
|
+
name: 'Existing',
|
|
100
|
+
email: 'test@example.com',
|
|
101
|
+
isActive: true,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
userRepository.findByEmail = async (email: string) =>
|
|
105
|
+
email === 'test@example.com' ? existingUser : null;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await userService.createUser({
|
|
109
|
+
name: 'New User',
|
|
110
|
+
email: 'test@example.com',
|
|
111
|
+
});
|
|
112
|
+
throw new Error('Should have thrown error');
|
|
113
|
+
} catch (error: any) {
|
|
114
|
+
expect(error.message).toContain('already exists');
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should update user', async () => {
|
|
119
|
+
const existingUser = {
|
|
120
|
+
id: 1,
|
|
121
|
+
name: 'John',
|
|
122
|
+
email: 'john@example.com',
|
|
123
|
+
isActive: true,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
userRepository.findById = async (id: number) =>
|
|
127
|
+
id === 1 ? existingUser : null;
|
|
128
|
+
userRepository.update = async (id: number, data: any) => ({
|
|
129
|
+
...existingUser,
|
|
130
|
+
...data,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const updated = await userService.updateUser(1, { name: 'John Updated' });
|
|
134
|
+
|
|
135
|
+
expect(updated?.name).toBe('John Updated');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should delete user', async () => {
|
|
139
|
+
const existingUser = {
|
|
140
|
+
id: 1,
|
|
141
|
+
name: 'John',
|
|
142
|
+
email: 'john@example.com',
|
|
143
|
+
isActive: true,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
userRepository.findById = async (id: number) =>
|
|
147
|
+
id === 1 ? existingUser : null;
|
|
148
|
+
|
|
149
|
+
const deleted = await userService.deleteUser(1);
|
|
150
|
+
|
|
151
|
+
expect(deleted).toBeTruthy();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should throw error when deleting non-existent user', async () => {
|
|
155
|
+
userRepository.findById = async () => null;
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
await userService.deleteUser(999);
|
|
159
|
+
throw new Error('Should have thrown error');
|
|
160
|
+
} catch (error: any) {
|
|
161
|
+
expect(error.message).toContain('not found');
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should request password reset', async () => {
|
|
166
|
+
const existingUser = {
|
|
167
|
+
id: 1,
|
|
168
|
+
name: 'John',
|
|
169
|
+
email: 'john@example.com',
|
|
170
|
+
isActive: true,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
userRepository.findByEmail = async (email: string) =>
|
|
174
|
+
email === 'john@example.com' ? existingUser : null;
|
|
175
|
+
|
|
176
|
+
let resetEmailSent = false;
|
|
177
|
+
let resetToken = '';
|
|
178
|
+
|
|
179
|
+
emailService.sendPasswordReset = async (email: string, token: string) => {
|
|
180
|
+
resetEmailSent = true;
|
|
181
|
+
resetToken = token;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
await userService.requestPasswordReset('john@example.com');
|
|
185
|
+
|
|
186
|
+
expect(resetEmailSent).toBeTruthy();
|
|
187
|
+
expect(resetToken).toBeTruthy();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// ============================================
|
|
192
|
+
// di-container.spec.ts
|
|
193
|
+
// Example test file for DI Container
|
|
194
|
+
// ============================================
|
|
195
|
+
|
|
196
|
+
describe('DIContainer', () => {
|
|
197
|
+
let container: DIContainer;
|
|
198
|
+
|
|
199
|
+
beforeEach(() => {
|
|
200
|
+
container = DIContainer.getInstance();
|
|
201
|
+
container.reset();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should resolve singleton instances', () => {
|
|
205
|
+
class TestService {
|
|
206
|
+
counter = 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
container.register(TestService);
|
|
210
|
+
|
|
211
|
+
const instance1 = container.resolve(TestService);
|
|
212
|
+
const instance2 = container.resolve(TestService);
|
|
213
|
+
|
|
214
|
+
instance1.counter = 5;
|
|
215
|
+
|
|
216
|
+
expect(instance2.counter).toBe(5);
|
|
217
|
+
expect(instance1).toBe(instance2);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should inject constructor dependencies', () => {
|
|
221
|
+
@Injectable()
|
|
222
|
+
class DependencyService {
|
|
223
|
+
getValue() {
|
|
224
|
+
return 'dependency';
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@Injectable()
|
|
229
|
+
class TestService {
|
|
230
|
+
constructor(private dep: DependencyService) {}
|
|
231
|
+
|
|
232
|
+
getDepValue() {
|
|
233
|
+
return this.dep.getValue();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
container.register(DependencyService);
|
|
238
|
+
container.register(TestService);
|
|
239
|
+
|
|
240
|
+
const service = container.resolve(TestService);
|
|
241
|
+
|
|
242
|
+
expect(service.getDepValue()).toBe('dependency');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should inject properties with @Autowired', () => {
|
|
246
|
+
@Injectable()
|
|
247
|
+
class DependencyService {
|
|
248
|
+
getValue() {
|
|
249
|
+
return 'autowired';
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@Injectable()
|
|
254
|
+
class TestService {
|
|
255
|
+
@Autowired()
|
|
256
|
+
private dep!: DependencyService;
|
|
257
|
+
|
|
258
|
+
getDepValue() {
|
|
259
|
+
return this.dep.getValue();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
container.register(DependencyService);
|
|
264
|
+
container.register(TestService);
|
|
265
|
+
|
|
266
|
+
const service = container.resolve(TestService);
|
|
267
|
+
|
|
268
|
+
expect(service.getDepValue()).toBe('autowired');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should inject values with @Value', () => {
|
|
272
|
+
process.env.TEST_VALUE = '42';
|
|
273
|
+
|
|
274
|
+
@Injectable()
|
|
275
|
+
class TestService {
|
|
276
|
+
@Value('${TEST_VALUE}')
|
|
277
|
+
private value!: number;
|
|
278
|
+
|
|
279
|
+
getValue() {
|
|
280
|
+
return this.value;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
container.register(TestService);
|
|
285
|
+
const service = container.resolve(TestService);
|
|
286
|
+
|
|
287
|
+
expect(service.getValue()).toBe(42);
|
|
288
|
+
|
|
289
|
+
delete process.env.TEST_VALUE;
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should detect circular dependencies', () => {
|
|
293
|
+
@Injectable()
|
|
294
|
+
class ServiceA {
|
|
295
|
+
constructor(private b: ServiceB) {}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@Injectable()
|
|
299
|
+
class ServiceB {
|
|
300
|
+
constructor(private a: ServiceA) {}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
container.register(ServiceA);
|
|
304
|
+
container.register(ServiceB);
|
|
305
|
+
|
|
306
|
+
expect(() => container.resolve(ServiceA)).toThrow('Circular dependency');
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// ============================================
|
|
311
|
+
// Helper functions for testing
|
|
312
|
+
// ============================================
|
|
313
|
+
|
|
314
|
+
// Mock functions for testing
|
|
315
|
+
global.beforeEach = (fn: () => void) => {
|
|
316
|
+
// Implement beforeEach hook
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
global.afterEach = (fn: () => void) => {
|
|
320
|
+
// Implement afterEach hook
|
|
321
|
+
};
|