@tehw0lf/yaft 0.0.15 → 0.0.17

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.
Files changed (51) hide show
  1. package/FeatureToggle.d.ts +20 -0
  2. package/FeatureToggle.js +72 -0
  3. package/README.md +25 -3
  4. package/evaluate.d.ts +42 -0
  5. package/evaluate.js +102 -0
  6. package/examples/ApiServiceBooleanProvider.d.ts +11 -0
  7. package/examples/ApiServiceBooleanProvider.js +85 -0
  8. package/examples/ApiServiceFeatureProvider.d.ts +17 -0
  9. package/examples/ApiServiceFeatureProvider.js +54 -0
  10. package/examples/LocalStorageBooleanProvider.d.ts +7 -0
  11. package/examples/LocalStorageBooleanProvider.js +23 -0
  12. package/examples/LocalStorageFeatureProvider.d.ts +14 -0
  13. package/examples/LocalStorageFeatureProvider.js +31 -0
  14. package/index.d.ts +3 -0
  15. package/index.js +14 -0
  16. package/mapping.d.ts +35 -0
  17. package/mapping.js +98 -0
  18. package/package.json +21 -9
  19. package/.github/workflows/build.yml +0 -28
  20. package/.github/workflows/security-scan.yml +0 -79
  21. package/CLAUDE.md +0 -151
  22. package/LICENSE +0 -21
  23. package/conformance.lock +0 -2
  24. package/jest.config.js +0 -16
  25. package/scripts/fetch-conformance.sh +0 -67
  26. package/src/FeatureToggle.ts +0 -95
  27. package/src/evaluate.ts +0 -122
  28. package/src/examples/ApiServiceBooleanProvider.ts +0 -82
  29. package/src/examples/ApiServiceFeatureProvider.ts +0 -52
  30. package/src/examples/LocalStorageBooleanProvider.ts +0 -25
  31. package/src/examples/LocalStorageFeatureProvider.ts +0 -31
  32. package/src/index.ts +0 -8
  33. package/src/mapping.ts +0 -92
  34. package/src/test/conformance-adapter/cases.ts +0 -104
  35. package/src/test/conformance-adapter/decorator.spec.ts +0 -376
  36. package/src/test/conformance-adapter/evaluation.spec.ts +0 -31
  37. package/src/test/conformance-adapter/mapping.spec.ts +0 -79
  38. package/src/test/decorator-behavior.spec.ts +0 -359
  39. package/src/test/error-handling.spec.ts +0 -396
  40. package/src/test/evaluate.spec.ts +0 -260
  41. package/src/test/feature-data.spec.ts +0 -102
  42. package/src/test/injectable-clock.spec.ts +0 -112
  43. package/src/test/integration.spec.ts +0 -472
  44. package/src/test/providers.spec.ts +0 -218
  45. package/src/test/test-boolean.json +0 -1
  46. package/src/test/test-feature.json +0 -20
  47. package/src/test/test-setup.ts +0 -2
  48. package/src/test/time-based-logic.spec.ts +0 -169
  49. package/src/test/yaft.demo.spec.ts +0 -53
  50. package/tsconfig.json +0 -14
  51. package/tsconfig.spec.json +0 -10
@@ -1,102 +0,0 @@
1
- import { Feature } from '../FeatureToggle';
2
-
3
- describe('Feature Data Type', () => {
4
- describe('Feature structure validation', () => {
5
- it('should accept valid Feature objects', () => {
6
- const validFeature: Feature = {
7
- key: 'testFeature',
8
- value: 'true',
9
- activeAt: '2023-01-01T00:00:00Z',
10
- disabledAt: '2023-12-31T23:59:59Z'
11
- };
12
-
13
- expect(validFeature.key).toBe('testFeature');
14
- expect(validFeature.value).toBe('true');
15
- expect(validFeature.activeAt).toBe('2023-01-01T00:00:00Z');
16
- expect(validFeature.disabledAt).toBe('2023-12-31T23:59:59Z');
17
- });
18
-
19
- it('should accept Feature with empty date strings', () => {
20
- const featureWithEmptyDates: Feature = {
21
- key: 'testFeature',
22
- value: 'false',
23
- activeAt: '',
24
- disabledAt: ''
25
- };
26
-
27
- expect(featureWithEmptyDates.activeAt).toBe('');
28
- expect(featureWithEmptyDates.disabledAt).toBe('');
29
- });
30
-
31
- it('should accept boolean-like string values', () => {
32
- const trueBooleanFeature: Feature = {
33
- key: 'trueFeature',
34
- value: 'true',
35
- activeAt: '',
36
- disabledAt: ''
37
- };
38
-
39
- const falseBooleanFeature: Feature = {
40
- key: 'falseFeature',
41
- value: 'false',
42
- activeAt: '',
43
- disabledAt: ''
44
- };
45
-
46
- expect(JSON.parse(trueBooleanFeature.value)).toBe(true);
47
- expect(JSON.parse(falseBooleanFeature.value)).toBe(false);
48
- });
49
-
50
- it('should handle various date formats', () => {
51
- const isoDateFeature: Feature = {
52
- key: 'isoFeature',
53
- value: 'true',
54
- activeAt: '2023-01-01T00:00:00.000Z',
55
- disabledAt: '2023-12-31T23:59:59.999Z'
56
- };
57
-
58
- const simpleDateFeature: Feature = {
59
- key: 'simpleFeature',
60
- value: 'true',
61
- activeAt: '2023-01-01',
62
- disabledAt: '2023-12-31'
63
- };
64
-
65
- expect(Date.parse(isoDateFeature.activeAt)).not.toBeNaN();
66
- expect(Date.parse(isoDateFeature.disabledAt)).not.toBeNaN();
67
- expect(Date.parse(simpleDateFeature.activeAt)).not.toBeNaN();
68
- expect(Date.parse(simpleDateFeature.disabledAt)).not.toBeNaN();
69
- });
70
- });
71
-
72
- describe('Feature collection structures', () => {
73
- it('should handle Record<string, Feature> collections', () => {
74
- const featureCollection: Record<string, Feature> = {
75
- 'feature1': {
76
- key: 'feature1',
77
- value: 'true',
78
- activeAt: '',
79
- disabledAt: ''
80
- },
81
- 'feature2': {
82
- key: 'feature2',
83
- value: 'false',
84
- activeAt: '2023-01-01',
85
- disabledAt: '2023-12-31'
86
- }
87
- };
88
-
89
- expect(Object.keys(featureCollection)).toHaveLength(2);
90
- expect(featureCollection['feature1']).toBeDefined();
91
- expect(featureCollection['feature2']).toBeDefined();
92
- expect(featureCollection['nonexistent']).toBeUndefined();
93
- });
94
-
95
- it('should handle empty feature collections', () => {
96
- const emptyCollection: Record<string, Feature> = {};
97
-
98
- expect(Object.keys(emptyCollection)).toHaveLength(0);
99
- expect(emptyCollection['anyKey']).toBeUndefined();
100
- });
101
- });
102
- });
@@ -1,112 +0,0 @@
1
- import { Clock } from '../evaluate';
2
- import { Feature } from '../FeatureToggle';
3
- import { ApiServiceFeatureProvider } from '../examples/ApiServiceFeatureProvider';
4
- import { LocalStorageFeatureProvider } from '../examples/LocalStorageFeatureProvider';
5
-
6
- /**
7
- * The clock has to be injectable for the conformance suite: every case there
8
- * carries its own `now`, and an adapter cannot set it if the providers read
9
- * `Date.now()` directly.
10
- *
11
- * These tests pin that down, and pin down that both feature-shaped providers
12
- * answer identically -- the point of moving the logic into evaluate().
13
- */
14
- describe('injectable clock', () => {
15
- const windowed: Feature = {
16
- key: 'windowed',
17
- value: 'true',
18
- activeAt: '2026-09-18T10:00:00Z',
19
- disabledAt: '2026-09-18T14:00:00Z',
20
- };
21
-
22
- const at = (iso: string): Clock => () => Date.parse(iso);
23
-
24
- /** A provider holding `windowed`, without touching the filesystem. */
25
- const localAt = (iso: string) => {
26
- const provider = new LocalStorageFeatureProvider('', at(iso));
27
- provider.data = { windowed };
28
- return provider;
29
- };
30
-
31
- /** The same, for the API provider, without touching the network. */
32
- const apiAt = (iso: string) => {
33
- const provider = new ApiServiceFeatureProvider('', '', at(iso));
34
- provider.data = { windowed };
35
- return provider;
36
- };
37
-
38
- describe('LocalStorageFeatureProvider', () => {
39
- it('is off before the window', () => {
40
- expect(localAt('2026-09-18T09:59:59Z').isEnabled('windowed')).toBe(false);
41
- });
42
-
43
- it('is on inside the window', () => {
44
- expect(localAt('2026-09-18T12:00:00Z').isEnabled('windowed')).toBe(true);
45
- });
46
-
47
- it('is off after the window', () => {
48
- expect(localAt('2026-09-18T14:00:00Z').isEnabled('windowed')).toBe(false);
49
- });
50
- });
51
-
52
- describe('ApiServiceFeatureProvider', () => {
53
- it('is off before the window', () => {
54
- expect(apiAt('2026-09-18T09:59:59Z').isEnabled('windowed')).toBe(false);
55
- });
56
-
57
- it('is on inside the window', () => {
58
- expect(apiAt('2026-09-18T12:00:00Z').isEnabled('windowed')).toBe(true);
59
- });
60
-
61
- it('is off after the window', () => {
62
- expect(apiAt('2026-09-18T14:00:00Z').isEnabled('windowed')).toBe(false);
63
- });
64
- });
65
-
66
- // Before this change each provider carried its own copy of the logic, so
67
- // they could drift apart unnoticed. Now they must agree everywhere.
68
- it('both feature providers agree at every instant', () => {
69
- for (const iso of [
70
- '2026-09-18T09:00:00Z',
71
- '2026-09-18T10:00:00Z',
72
- '2026-09-18T12:00:00Z',
73
- '2026-09-18T13:59:59Z',
74
- '2026-09-18T14:00:00Z',
75
- '2026-09-18T20:00:00Z',
76
- ]) {
77
- expect(localAt(iso).isEnabled('windowed')).toBe(
78
- apiAt(iso).isEnabled('windowed')
79
- );
80
- }
81
- });
82
-
83
- it('the same provider answers differently as its clock advances', () => {
84
- let now = Date.parse('2026-09-18T09:00:00Z');
85
- const provider = new LocalStorageFeatureProvider('', () => now);
86
- provider.data = { windowed };
87
-
88
- expect(provider.isEnabled('windowed')).toBe(false);
89
-
90
- now = Date.parse('2026-09-18T12:00:00Z');
91
- expect(provider.isEnabled('windowed')).toBe(true);
92
-
93
- now = Date.parse('2026-09-18T15:00:00Z');
94
- expect(provider.isEnabled('windowed')).toBe(false);
95
- });
96
-
97
- it('defaults to the system clock when none is given', () => {
98
- const provider = new LocalStorageFeatureProvider('');
99
- provider.data = {
100
- open: { key: 'open', value: 'true', activeAt: '', disabledAt: '' },
101
- expired: {
102
- key: 'expired',
103
- value: 'true',
104
- activeAt: '',
105
- disabledAt: '2000-01-01T00:00:00Z',
106
- },
107
- };
108
-
109
- expect(provider.isEnabled('open')).toBe(true);
110
- expect(provider.isEnabled('expired')).toBe(false);
111
- });
112
- });
@@ -1,472 +0,0 @@
1
- import { FeatureToggle, FeatureToggleBase, Feature } from '../FeatureToggle';
2
- import { LocalStorageFeatureProvider } from '../examples/LocalStorageFeatureProvider';
3
- import { LocalStorageBooleanProvider } from '../examples/LocalStorageBooleanProvider';
4
-
5
- describe('Integration Tests', () => {
6
- describe('Real-world Usage Scenarios', () => {
7
- describe('E-commerce Feature Toggles', () => {
8
- beforeEach(() => {
9
- // Mock feature configuration for e-commerce scenarios
10
- const ecommerceFeatures: Record<string, Feature> = {
11
- 'checkout_v2': {
12
- key: 'checkout_v2',
13
- value: 'true',
14
- activeAt: new Date(Date.now() - 86400000).toISOString(), // 1 day ago
15
- disabledAt: new Date(Date.now() + 86400000).toISOString() // 1 day from now
16
- },
17
- 'premium_features': {
18
- key: 'premium_features',
19
- value: 'false',
20
- activeAt: '',
21
- disabledAt: ''
22
- },
23
- 'beta_ui': {
24
- key: 'beta_ui',
25
- value: 'true',
26
- activeAt: new Date(Date.now() - 86400000).toISOString(), // 1 day ago
27
- disabledAt: new Date(Date.now() + 86400000).toISOString() // 1 day from now
28
- }
29
- };
30
-
31
- const provider = new (class extends LocalStorageFeatureProvider {
32
- constructor() {
33
- super('');
34
- this.data = ecommerceFeatures;
35
- }
36
- })();
37
-
38
- FeatureToggleBase.featureProvider = provider;
39
- });
40
-
41
- it('should handle checkout flow feature toggle', () => {
42
- class LegacyCheckout {
43
- process(items: string[]) {
44
- return `Legacy checkout processing ${items.length} items`;
45
- }
46
- }
47
-
48
- class NewCheckout {
49
- process(items: string[]) {
50
- return `New checkout v2 processing ${items.length} items`;
51
- }
52
- }
53
-
54
- @FeatureToggle('checkout_v2', LegacyCheckout)
55
- class CheckoutService {
56
- process(items: string[]) {
57
- return `Enhanced checkout processing ${items.length} items`;
58
- }
59
- }
60
-
61
- const checkout = new CheckoutService();
62
- const result = checkout.process(['item1', 'item2']);
63
-
64
- expect(result).toBe('Enhanced checkout processing 2 items');
65
- });
66
-
67
- it('should handle premium features with method-level toggles', () => {
68
- function basicRecommendations() {
69
- return ['Popular item 1', 'Popular item 2'];
70
- }
71
-
72
- class RecommendationService {
73
- @FeatureToggle('premium_features', basicRecommendations)
74
- getPersonalizedRecommendations(userId: string) {
75
- return [`AI recommendation for ${userId}`, `Premium suggestion for ${userId}`];
76
- }
77
-
78
- getBasicRecommendations() {
79
- return ['Basic item 1', 'Basic item 2'];
80
- }
81
- }
82
-
83
- const service = new RecommendationService();
84
- const recommendations = service.getPersonalizedRecommendations('user123');
85
-
86
- expect(recommendations).toEqual(['Popular item 1', 'Popular item 2']);
87
- });
88
-
89
- it('should handle UI feature toggles', () => {
90
- class StandardUI {
91
- render() {
92
- return '<div>Standard UI</div>';
93
- }
94
- }
95
-
96
- @FeatureToggle('beta_ui', StandardUI)
97
- class BetaUI {
98
- render() {
99
- return '<div>Beta UI with new features</div>';
100
- }
101
- }
102
-
103
- const ui = new BetaUI();
104
- const rendered = ui.render();
105
-
106
- expect(rendered).toBe('<div>Beta UI with new features</div>');
107
- });
108
- });
109
-
110
- describe('Microservice Feature Coordination', () => {
111
- it('should handle multiple services with coordinated feature toggles', () => {
112
- const serviceFeatures = {
113
- 'service_a_feature': true,
114
- 'service_b_feature': false,
115
- 'shared_feature': true
116
- };
117
-
118
- const provider = new (class extends LocalStorageBooleanProvider {
119
- constructor() {
120
- super('');
121
- this.data = serviceFeatures;
122
- }
123
- })();
124
-
125
- FeatureToggleBase.featureProvider = provider;
126
-
127
- class ServiceA {
128
- @FeatureToggle('service_a_feature')
129
- processData(data: any) {
130
- return `ServiceA: Enhanced processing of ${JSON.stringify(data)}`;
131
- }
132
-
133
- @FeatureToggle('shared_feature')
134
- sharedFunction() {
135
- return 'ServiceA: Shared feature active';
136
- }
137
- }
138
-
139
- class ServiceB {
140
- @FeatureToggle('service_b_feature')
141
- transformData(data: any) {
142
- return `ServiceB: New transformation of ${JSON.stringify(data)}`;
143
- }
144
-
145
- @FeatureToggle('shared_feature')
146
- sharedFunction() {
147
- return 'ServiceB: Shared feature active';
148
- }
149
- }
150
-
151
- const serviceA = new ServiceA();
152
- const serviceB = new ServiceB();
153
-
154
- expect(serviceA.processData({ test: true })).toBe('ServiceA: Enhanced processing of {"test":true}');
155
- expect(serviceB.transformData({ test: true })).toBeUndefined(); // Feature disabled
156
- expect(serviceA.sharedFunction()).toBe('ServiceA: Shared feature active');
157
- expect(serviceB.sharedFunction()).toBe('ServiceB: Shared feature active');
158
- });
159
- });
160
-
161
- describe('A/B Testing Scenarios', () => {
162
- it('should support A/B testing with multiple variants', () => {
163
- // This test demonstrates A/B testing where the feature evaluation
164
- // happens at class definition time, not method call time
165
-
166
- function variantA() {
167
- return 'Algorithm A result';
168
- }
169
-
170
- function variantB() {
171
- return 'Algorithm B result';
172
- }
173
-
174
- const abTestFeatures = {
175
- 'algorithm_variant_a': false,
176
- 'algorithm_variant_b': true
177
- };
178
-
179
- const provider = new (class extends LocalStorageBooleanProvider {
180
- constructor() {
181
- super('');
182
- this.data = abTestFeatures;
183
- }
184
- })();
185
-
186
- // Set provider BEFORE defining the class (critical for decorator evaluation)
187
- FeatureToggleBase.featureProvider = provider;
188
-
189
- // Define class with enabled variant
190
- class AlgorithmB {
191
- @FeatureToggle('algorithm_variant_b', variantB)
192
- calculate(input: number) {
193
- return 'Default algorithm result';
194
- }
195
- }
196
-
197
- // Define class with disabled variant
198
- class AlgorithmA {
199
- @FeatureToggle('algorithm_variant_a', variantA)
200
- calculate(input: number) {
201
- return 'Default algorithm result';
202
- }
203
- }
204
-
205
- const algorithmB = new AlgorithmB();
206
- const algorithmA = new AlgorithmA();
207
-
208
- const resultB = algorithmB.calculate(42);
209
- const resultA = algorithmA.calculate(42);
210
-
211
- // Enabled variant should use original method (returns "Default algorithm result")
212
- // Disabled variant should use fallback method
213
- expect(resultB).toBe('Default algorithm result'); // enabled, uses original
214
- expect(resultA).toBe('Algorithm A result'); // disabled, uses fallback
215
- });
216
- });
217
- });
218
-
219
- describe('Performance and Load Testing', () => {
220
- it('should handle high-frequency feature toggle evaluations', () => {
221
- const provider = new (class extends LocalStorageBooleanProvider {
222
- constructor() {
223
- super('');
224
- this.data = { 'myToggle': true };
225
- }
226
- })();
227
- FeatureToggleBase.featureProvider = provider;
228
-
229
- class HighFrequencyService {
230
- @FeatureToggle('myToggle')
231
- processItem(item: any) {
232
- return `Processed: ${item}`;
233
- }
234
- }
235
-
236
- const service = new HighFrequencyService();
237
- const items = Array.from({ length: 1000 }, (_, i) => `item${i}`);
238
-
239
- const start = performance.now();
240
- const results = items.map(item => service.processItem(item));
241
- const end = performance.now();
242
-
243
- expect(results).toHaveLength(1000);
244
- expect(results[0]).toBe('Processed: item0');
245
- expect(results[999]).toBe('Processed: item999');
246
-
247
- // Performance check - should complete within reasonable time
248
- expect(end - start).toBeLessThan(100); // 100ms for 1000 operations
249
- });
250
-
251
- it('should handle concurrent access to feature toggles', async () => {
252
- const provider = new (class extends LocalStorageBooleanProvider {
253
- constructor() {
254
- super('');
255
- this.data = { 'myToggle': true };
256
- }
257
- })();
258
- FeatureToggleBase.featureProvider = provider;
259
-
260
- class ConcurrentService {
261
- @FeatureToggle('myToggle')
262
- async asyncProcess(id: number) {
263
- await new Promise(resolve => setTimeout(resolve, 1));
264
- return `Async result ${id}`;
265
- }
266
- }
267
-
268
- const service = new ConcurrentService();
269
- const promises = Array.from({ length: 50 }, (_, i) => service.asyncProcess(i));
270
-
271
- const results = await Promise.all(promises);
272
-
273
- expect(results).toHaveLength(50);
274
- expect(results[0]).toBe('Async result 0');
275
- expect(results[49]).toBe('Async result 49');
276
- });
277
- });
278
-
279
- describe('Cross-Provider Compatibility', () => {
280
- it('should work consistently across different provider types', () => {
281
- // Test with Feature provider using mock data that enables myMethodToggle
282
- const featureProvider = new (class extends LocalStorageFeatureProvider {
283
- constructor() {
284
- super('');
285
- this.data = {
286
- 'myMethodToggle': {
287
- key: 'myMethodToggle',
288
- value: 'true',
289
- activeAt: new Date(Date.now() - 86400000).toISOString(),
290
- disabledAt: new Date(Date.now() + 86400000).toISOString()
291
- }
292
- };
293
- }
294
- })();
295
- FeatureToggleBase.featureProvider = featureProvider;
296
-
297
- class TestService {
298
- @FeatureToggle('myMethodToggle')
299
- testMethod() {
300
- return 'feature provider result';
301
- }
302
- }
303
-
304
- let service = new TestService();
305
- let result = service.testMethod();
306
- expect(result).toBe('feature provider result');
307
-
308
- // Switch to Boolean provider
309
- const booleanProvider = new (class extends LocalStorageBooleanProvider {
310
- constructor() {
311
- super('');
312
- this.data = { 'myToggle': true };
313
- }
314
- })();
315
- FeatureToggleBase.featureProvider = booleanProvider;
316
-
317
- // Create new class instance to trigger re-evaluation
318
- class TestService2 {
319
- @FeatureToggle('myToggle')
320
- testMethod() {
321
- return 'boolean provider result';
322
- }
323
- }
324
-
325
- service = new TestService2();
326
- result = service.testMethod();
327
- expect(result).toBe('boolean provider result');
328
- });
329
- });
330
-
331
- describe('Real-world Integration Patterns', () => {
332
- it('should support factory pattern with feature toggles', () => {
333
- const factoryFeatures = {
334
- 'use_redis_cache': true,
335
- 'use_memory_cache': false
336
- };
337
-
338
- const provider = new (class extends LocalStorageBooleanProvider {
339
- constructor() {
340
- super('');
341
- this.data = factoryFeatures;
342
- }
343
- })();
344
-
345
- FeatureToggleBase.featureProvider = provider;
346
-
347
- class MemoryCache {
348
- get(key: string) { return `memory:${key}`; }
349
- }
350
-
351
- class RedisCache {
352
- get(key: string) { return `redis:${key}`; }
353
- }
354
-
355
- class CacheFactory {
356
- @FeatureToggle('use_redis_cache', () => new MemoryCache())
357
- createCache() {
358
- return new RedisCache();
359
- }
360
- }
361
-
362
- const factory = new CacheFactory();
363
- const cache = factory.createCache();
364
-
365
- expect(cache).toBeInstanceOf(RedisCache);
366
- expect(cache.get('test')).toBe('redis:test');
367
- });
368
-
369
- it('should support middleware pattern with feature toggles', () => {
370
- const middlewareFeatures = {
371
- 'auth_middleware': true,
372
- 'logging_middleware': false,
373
- 'rate_limit_middleware': true
374
- };
375
-
376
- const provider = new (class extends LocalStorageBooleanProvider {
377
- constructor() {
378
- super('');
379
- this.data = middlewareFeatures;
380
- }
381
- })();
382
-
383
- FeatureToggleBase.featureProvider = provider;
384
-
385
- class RequestProcessor {
386
- private logs: string[] = [];
387
-
388
- @FeatureToggle('auth_middleware')
389
- authenticate(request: any) {
390
- this.logs.push('auth executed');
391
- return { ...request, authenticated: true };
392
- }
393
-
394
- @FeatureToggle('logging_middleware')
395
- log(request: any) {
396
- this.logs.push('logging executed');
397
- return request;
398
- }
399
-
400
- @FeatureToggle('rate_limit_middleware')
401
- rateLimit(request: any) {
402
- this.logs.push('rate limit executed');
403
- return request;
404
- }
405
-
406
- process(request: any) {
407
- let processedRequest = request;
408
- processedRequest = this.authenticate(processedRequest) || processedRequest;
409
- processedRequest = this.log(processedRequest) || processedRequest;
410
- processedRequest = this.rateLimit(processedRequest) || processedRequest;
411
- return processedRequest;
412
- }
413
-
414
- getLogs() { return this.logs; }
415
- }
416
-
417
- const processor = new RequestProcessor();
418
- const result = processor.process({ id: 1 });
419
-
420
- expect(result.authenticated).toBe(true);
421
- expect(processor.getLogs()).toEqual(['auth executed', 'rate limit executed']);
422
- });
423
-
424
- it('should support plugin architecture with feature toggles', () => {
425
- const pluginFeatures = {
426
- 'analytics_plugin': false,
427
- 'social_sharing_plugin': true,
428
- 'comments_plugin': true
429
- };
430
-
431
- const provider = new (class extends LocalStorageBooleanProvider {
432
- constructor() {
433
- super('');
434
- this.data = pluginFeatures;
435
- }
436
- })();
437
-
438
- FeatureToggleBase.featureProvider = provider;
439
-
440
- class PluginManager {
441
- private activePlugins: string[] = [];
442
-
443
- @FeatureToggle('analytics_plugin')
444
- loadAnalytics() {
445
- this.activePlugins.push('analytics');
446
- }
447
-
448
- @FeatureToggle('social_sharing_plugin')
449
- loadSocialSharing() {
450
- this.activePlugins.push('social-sharing');
451
- }
452
-
453
- @FeatureToggle('comments_plugin')
454
- loadComments() {
455
- this.activePlugins.push('comments');
456
- }
457
-
458
- initialize() {
459
- this.loadAnalytics();
460
- this.loadSocialSharing();
461
- this.loadComments();
462
- return this.activePlugins;
463
- }
464
- }
465
-
466
- const manager = new PluginManager();
467
- const activePlugins = manager.initialize();
468
-
469
- expect(activePlugins).toEqual(['social-sharing', 'comments']);
470
- });
471
- });
472
- });