bunsane 0.3.1 → 0.4.0
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/CHANGELOG.md +445 -318
- package/config/cache.config.ts +35 -1
- package/core/App.ts +24 -1064
- package/core/ArcheType.ts +78 -2110
- package/core/BatchLoader.ts +56 -32
- package/core/Entity.ts +85 -1043
- package/core/EntityHookManager.ts +52 -754
- package/core/Logger.ts +10 -0
- package/core/RequestContext.ts +64 -6
- package/core/RequestLoaders.ts +187 -36
- package/core/SchedulerManager.ts +28 -600
- package/core/app/bootstrap.ts +133 -0
- package/core/app/cors.ts +85 -0
- package/core/app/graphqlSetup.ts +56 -0
- package/core/app/healthEndpoints.ts +31 -0
- package/core/app/metricsCollector.ts +27 -0
- package/core/app/preparedStatementWarmup.ts +15 -0
- package/core/app/processHandlers.ts +43 -0
- package/core/app/requestRouter.ts +310 -0
- package/core/app/restRegistry.ts +80 -0
- package/core/app/shutdown.ts +97 -0
- package/core/app/studioRouter.ts +83 -0
- package/core/archetype/customTypes.ts +100 -0
- package/core/archetype/decorators.ts +171 -0
- package/core/archetype/fieldResolvers.ts +666 -0
- package/core/archetype/helpers.ts +29 -0
- package/core/archetype/relationLoader.ts +161 -0
- package/core/archetype/schemaBuilder.ts +141 -0
- package/core/archetype/weaver.ts +218 -0
- package/core/archetype/zodSchemaBuilder.ts +527 -0
- package/core/cache/CacheManager.ts +173 -267
- package/core/cache/CompressionUtils.ts +34 -3
- package/core/cache/MemoryCache.ts +40 -37
- package/core/cache/RedisCache.ts +4 -4
- package/core/cache/health.ts +30 -0
- package/core/cache/invalidation.ts +96 -0
- package/core/cache/strategies/writeInvalidate.ts +111 -0
- package/core/cache/strategies/writeThrough.ts +233 -0
- package/core/components/BaseComponent.ts +16 -8
- package/core/components/ComponentRegistry.ts +28 -0
- package/core/decorators/IndexedField.ts +1 -1
- package/core/entity/cacheStrategies.ts +97 -0
- package/core/entity/componentAccess.ts +364 -0
- package/core/entity/finders.ts +202 -0
- package/core/entity/pendingOps.ts +72 -0
- package/core/entity/saveEntity.ts +377 -0
- package/core/hooks/dispatcher.ts +439 -0
- package/core/hooks/guards.ts +155 -0
- package/core/hooks/registry.ts +247 -0
- package/core/metadata/definitions/Component.ts +1 -1
- package/core/metadata/index.ts +15 -4
- package/core/middleware/AccessLog.ts +8 -1
- package/core/middleware/RateLimit.ts +102 -105
- package/core/middleware/RequestId.ts +2 -9
- package/core/middleware/SecurityHeaders.ts +2 -11
- package/core/middleware/headers.ts +28 -0
- package/core/remote/OutboxWorker.ts +213 -183
- package/core/remote/RemoteManager.ts +401 -400
- package/core/remote/types.ts +153 -151
- package/core/requestScope.ts +34 -0
- package/core/scheduler/cronEvaluator.ts +174 -0
- package/core/scheduler/lifecycleHooks.ts +21 -0
- package/core/scheduler/lockCoordinator.ts +27 -0
- package/core/scheduler/metrics.ts +14 -0
- package/core/scheduler/taskRunner.ts +420 -0
- package/database/DatabaseHelper.ts +128 -101
- package/database/IndexingStrategy.ts +72 -2
- package/database/PreparedStatementCache.ts +20 -5
- package/database/cancellable.ts +35 -0
- package/database/index.ts +15 -3
- package/database/instrumentedDb.ts +141 -0
- package/endpoints/archetypes.ts +2 -8
- package/endpoints/tables.ts +6 -1
- package/gql/index.ts +1 -1
- package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
- package/package.json +22 -1
- package/query/CTENode.ts +5 -3
- package/query/ComponentInclusionNode.ts +240 -13
- package/query/OrNode.ts +6 -5
- package/query/Query.ts +203 -59
- package/query/QueryContext.ts +6 -0
- package/query/QueryDAG.ts +7 -2
- package/query/membershipSource.ts +66 -0
- package/storage/LocalStorageProvider.ts +8 -3
- package/studio/dist/assets/index-BMZ67Npg.js +254 -0
- package/studio/dist/assets/index-BpbuYz9g.css +1 -0
- package/studio/{index.html → dist/index.html} +3 -2
- package/swagger/generator.ts +11 -1
- package/upload/UploadManager.ts +8 -6
- package/utils/uuid.ts +40 -10
- package/.claude/settings.local.json +0 -47
- package/.prettierrc +0 -4
- package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
- package/.serena/memories/architecture.md +0 -154
- package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
- package/.serena/memories/code_style_and_conventions.md +0 -76
- package/.serena/memories/project_overview.md +0 -43
- package/.serena/memories/schema-dsl-plan.md +0 -107
- package/.serena/memories/suggested_commands.md +0 -80
- package/.serena/memories/typescript-compilation-status.md +0 -54
- package/.serena/project.yml +0 -114
- package/BunSane.jpg +0 -0
- package/CLAUDE.md +0 -198
- package/TODO.md +0 -2
- package/bun.lock +0 -302
- package/bunfig.toml +0 -10
- package/docs/SCALABILITY_PLAN.md +0 -175
- package/studio/bun.lock +0 -482
- package/studio/package.json +0 -39
- package/studio/postcss.config.js +0 -6
- package/studio/src/components/DataTable.tsx +0 -211
- package/studio/src/components/Layout.tsx +0 -13
- package/studio/src/components/PageContainer.tsx +0 -9
- package/studio/src/components/PageHeader.tsx +0 -13
- package/studio/src/components/SearchBar.tsx +0 -57
- package/studio/src/components/Sidebar.tsx +0 -294
- package/studio/src/components/ui/button.tsx +0 -56
- package/studio/src/components/ui/checkbox.tsx +0 -26
- package/studio/src/components/ui/input.tsx +0 -25
- package/studio/src/hooks/useDataTable.ts +0 -131
- package/studio/src/index.css +0 -36
- package/studio/src/lib/api.ts +0 -186
- package/studio/src/lib/utils.ts +0 -13
- package/studio/src/main.tsx +0 -17
- package/studio/src/pages/ArcheType.tsx +0 -239
- package/studio/src/pages/Components.tsx +0 -124
- package/studio/src/pages/EntityInspector.tsx +0 -302
- package/studio/src/pages/QueryRunner.tsx +0 -246
- package/studio/src/pages/Table.tsx +0 -94
- package/studio/src/pages/Welcome.tsx +0 -241
- package/studio/src/routes.tsx +0 -45
- package/studio/src/store/archeTypeSettings.ts +0 -30
- package/studio/src/store/studio.ts +0 -65
- package/studio/src/utils/columnHelpers.tsx +0 -114
- package/studio/studio-instructions.md +0 -81
- package/studio/tailwind.config.js +0 -77
- package/studio/utils.ts +0 -54
- package/studio/vite.config.js +0 -19
- package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
- package/tests/benchmark/bunfig.toml +0 -9
- package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
- package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
- package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
- package/tests/benchmark/fixtures/index.ts +0 -6
- package/tests/benchmark/index.ts +0 -22
- package/tests/benchmark/noop-preload.ts +0 -3
- package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
- package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
- package/tests/benchmark/runners/index.ts +0 -4
- package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
- package/tests/benchmark/scripts/generate-db.ts +0 -344
- package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
- package/tests/e2e/http.test.ts +0 -130
- package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
- package/tests/fixtures/components/TestOrder.ts +0 -23
- package/tests/fixtures/components/TestProduct.ts +0 -23
- package/tests/fixtures/components/TestUser.ts +0 -20
- package/tests/fixtures/components/index.ts +0 -6
- package/tests/graphql/SchemaGeneration.test.ts +0 -90
- package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
- package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
- package/tests/helpers/MockRedisClient.ts +0 -113
- package/tests/helpers/MockRedisStreamServer.ts +0 -448
- package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
- package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
- package/tests/integration/entity/Entity.persistence.test.ts +0 -333
- package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
- package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
- package/tests/integration/query/Query.edgeCases.test.ts +0 -595
- package/tests/integration/query/Query.exec.test.ts +0 -576
- package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
- package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
- package/tests/integration/remote/dlq.test.ts +0 -175
- package/tests/integration/remote/event-dispatch.test.ts +0 -114
- package/tests/integration/remote/outbox.test.ts +0 -130
- package/tests/integration/remote/rpc.test.ts +0 -177
- package/tests/pglite-setup.ts +0 -62
- package/tests/setup.ts +0 -164
- package/tests/stress/BenchmarkRunner.ts +0 -203
- package/tests/stress/DataSeeder.ts +0 -190
- package/tests/stress/StressTestReporter.ts +0 -229
- package/tests/stress/cursor-perf-test.ts +0 -171
- package/tests/stress/fixtures/RealisticComponents.ts +0 -235
- package/tests/stress/fixtures/StressTestComponents.ts +0 -58
- package/tests/stress/index.ts +0 -7
- package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
- package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
- package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
- package/tests/unit/BatchLoader.test.ts +0 -196
- package/tests/unit/archetype/ArcheType.test.ts +0 -107
- package/tests/unit/cache/CacheManager.test.ts +0 -367
- package/tests/unit/cache/MemoryCache.test.ts +0 -260
- package/tests/unit/cache/RedisCache.test.ts +0 -411
- package/tests/unit/entity/Entity.components.test.ts +0 -317
- package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
- package/tests/unit/entity/Entity.reload.test.ts +0 -63
- package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
- package/tests/unit/entity/Entity.test.ts +0 -345
- package/tests/unit/gql/depthLimit.test.ts +0 -203
- package/tests/unit/gql/operationMiddleware.test.ts +0 -293
- package/tests/unit/health/Health.test.ts +0 -129
- package/tests/unit/middleware/AccessLog.test.ts +0 -37
- package/tests/unit/middleware/Middleware.test.ts +0 -98
- package/tests/unit/middleware/RequestId.test.ts +0 -54
- package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
- package/tests/unit/query/FilterBuilder.test.ts +0 -111
- package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
- package/tests/unit/query/Query.emptyString.test.ts +0 -69
- package/tests/unit/query/Query.test.ts +0 -310
- package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
- package/tests/unit/remote/RemoteError.test.ts +0 -55
- package/tests/unit/remote/decorators.test.ts +0 -195
- package/tests/unit/remote/metrics.test.ts +0 -115
- package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
- package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
- package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
- package/tests/unit/schema/schema-integration.test.ts +0 -426
- package/tests/unit/schema/schema.test.ts +0 -580
- package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
- package/tests/unit/upload/RestUpload.test.ts +0 -267
- package/tests/unit/validateEnv.test.ts +0 -82
- package/tests/utils/entity-tracker.ts +0 -57
- package/tests/utils/index.ts +0 -13
- package/tests/utils/test-context.ts +0 -149
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for DistributedLock
|
|
3
|
-
* Tests PostgreSQL advisory lock-based distributed locking functionality
|
|
4
|
-
*
|
|
5
|
-
* Note: These tests require a PostgreSQL database connection
|
|
6
|
-
*/
|
|
7
|
-
import { describe, test, expect, beforeEach, afterEach, beforeAll, afterAll } from 'bun:test';
|
|
8
|
-
import { DistributedLock, resetDistributedLock, DEFAULT_LOCK_CONFIG } from '../../../core/scheduler/DistributedLock';
|
|
9
|
-
|
|
10
|
-
describe('DistributedLock', () => {
|
|
11
|
-
let lock: DistributedLock;
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
// Reset singleton before each test
|
|
15
|
-
resetDistributedLock();
|
|
16
|
-
lock = new DistributedLock({
|
|
17
|
-
enabled: true,
|
|
18
|
-
enableLogging: false,
|
|
19
|
-
lockTimeout: 0,
|
|
20
|
-
retryInterval: 50,
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterEach(async () => {
|
|
25
|
-
// Release all locks and reset
|
|
26
|
-
await lock.releaseAll();
|
|
27
|
-
resetDistributedLock();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('constructor', () => {
|
|
31
|
-
test('creates lock with default config', () => {
|
|
32
|
-
const defaultLock = new DistributedLock();
|
|
33
|
-
expect(defaultLock).toBeDefined();
|
|
34
|
-
expect(defaultLock.getConfig().enabled).toBe(DEFAULT_LOCK_CONFIG.enabled);
|
|
35
|
-
expect(defaultLock.getConfig().lockKeyPrefix).toBe(DEFAULT_LOCK_CONFIG.lockKeyPrefix);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('creates lock with custom config', () => {
|
|
39
|
-
const customLock = new DistributedLock({
|
|
40
|
-
enabled: false,
|
|
41
|
-
lockKeyPrefix: 0x12345678,
|
|
42
|
-
lockTimeout: 5000,
|
|
43
|
-
});
|
|
44
|
-
const config = customLock.getConfig();
|
|
45
|
-
expect(config.enabled).toBe(false);
|
|
46
|
-
expect(config.lockKeyPrefix).toBe(0x12345678);
|
|
47
|
-
expect(config.lockTimeout).toBe(5000);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('tryAcquire()', () => {
|
|
52
|
-
test('acquires lock for new task', async () => {
|
|
53
|
-
const result = await lock.tryAcquire('test-task-1');
|
|
54
|
-
|
|
55
|
-
expect(result.acquired).toBe(true);
|
|
56
|
-
expect(result.taskId).toBe('test-task-1');
|
|
57
|
-
expect(result.lockKey).toBeDefined();
|
|
58
|
-
expect(typeof result.lockKey).toBe('bigint');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('generates consistent lock keys for same task', async () => {
|
|
62
|
-
const lock2 = new DistributedLock({ enabled: true, enableLogging: false });
|
|
63
|
-
|
|
64
|
-
const result1 = await lock.tryAcquire('consistent-task');
|
|
65
|
-
await lock.release('consistent-task');
|
|
66
|
-
|
|
67
|
-
const result2 = await lock2.tryAcquire('consistent-task');
|
|
68
|
-
await lock2.release('consistent-task');
|
|
69
|
-
|
|
70
|
-
expect(result1.lockKey).toBe(result2.lockKey);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('generates different lock keys for different tasks', async () => {
|
|
74
|
-
const result1 = await lock.tryAcquire('task-a');
|
|
75
|
-
const result2 = await lock.tryAcquire('task-b');
|
|
76
|
-
|
|
77
|
-
expect(result1.lockKey).not.toBe(result2.lockKey);
|
|
78
|
-
|
|
79
|
-
await lock.release('task-a');
|
|
80
|
-
await lock.release('task-b');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('fails to acquire already held lock', async () => {
|
|
84
|
-
// First instance acquires lock
|
|
85
|
-
const result1 = await lock.tryAcquire('exclusive-task');
|
|
86
|
-
expect(result1.acquired).toBe(true);
|
|
87
|
-
|
|
88
|
-
// Second instance tries to acquire same lock (simulated with same connection)
|
|
89
|
-
// Note: In real scenario, this would be a different database session
|
|
90
|
-
// For unit test, we verify the lock is tracked as held
|
|
91
|
-
expect(lock.isHeld('exclusive-task')).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('returns true immediately when locking disabled', async () => {
|
|
95
|
-
const disabledLock = new DistributedLock({ enabled: false });
|
|
96
|
-
|
|
97
|
-
const result = await lock.tryAcquire('disabled-test');
|
|
98
|
-
|
|
99
|
-
// When disabled, lock is always "acquired" with key 0
|
|
100
|
-
const disabledResult = await disabledLock.tryAcquire('disabled-test');
|
|
101
|
-
expect(disabledResult.acquired).toBe(true);
|
|
102
|
-
expect(disabledResult.lockKey).toBe(0n);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('tracks held locks locally', async () => {
|
|
106
|
-
expect(lock.getHeldLockCount()).toBe(0);
|
|
107
|
-
|
|
108
|
-
await lock.tryAcquire('tracked-task-1');
|
|
109
|
-
expect(lock.getHeldLockCount()).toBe(1);
|
|
110
|
-
expect(lock.isHeld('tracked-task-1')).toBe(true);
|
|
111
|
-
|
|
112
|
-
await lock.tryAcquire('tracked-task-2');
|
|
113
|
-
expect(lock.getHeldLockCount()).toBe(2);
|
|
114
|
-
expect(lock.isHeld('tracked-task-2')).toBe(true);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('release()', () => {
|
|
119
|
-
test('releases held lock', async () => {
|
|
120
|
-
await lock.tryAcquire('release-test');
|
|
121
|
-
expect(lock.isHeld('release-test')).toBe(true);
|
|
122
|
-
|
|
123
|
-
const released = await lock.release('release-test');
|
|
124
|
-
|
|
125
|
-
expect(released).toBe(true);
|
|
126
|
-
expect(lock.isHeld('release-test')).toBe(false);
|
|
127
|
-
expect(lock.getHeldLockCount()).toBe(0);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('returns false for non-held lock', async () => {
|
|
131
|
-
const released = await lock.release('never-acquired');
|
|
132
|
-
|
|
133
|
-
// PostgreSQL returns false if lock wasn't held
|
|
134
|
-
expect(released).toBe(false);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('does nothing when disabled', async () => {
|
|
138
|
-
const disabledLock = new DistributedLock({ enabled: false });
|
|
139
|
-
|
|
140
|
-
const released = await disabledLock.release('any-task');
|
|
141
|
-
|
|
142
|
-
expect(released).toBe(true);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe('releaseAll()', () => {
|
|
147
|
-
test('releases all held locks', async () => {
|
|
148
|
-
await lock.tryAcquire('multi-1');
|
|
149
|
-
await lock.tryAcquire('multi-2');
|
|
150
|
-
await lock.tryAcquire('multi-3');
|
|
151
|
-
|
|
152
|
-
expect(lock.getHeldLockCount()).toBe(3);
|
|
153
|
-
|
|
154
|
-
await lock.releaseAll();
|
|
155
|
-
|
|
156
|
-
expect(lock.getHeldLockCount()).toBe(0);
|
|
157
|
-
expect(lock.isHeld('multi-1')).toBe(false);
|
|
158
|
-
expect(lock.isHeld('multi-2')).toBe(false);
|
|
159
|
-
expect(lock.isHeld('multi-3')).toBe(false);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test('handles empty lock set gracefully', async () => {
|
|
163
|
-
expect(lock.getHeldLockCount()).toBe(0);
|
|
164
|
-
|
|
165
|
-
// Should not throw
|
|
166
|
-
await lock.releaseAll();
|
|
167
|
-
|
|
168
|
-
expect(lock.getHeldLockCount()).toBe(0);
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
describe('updateConfig()', () => {
|
|
173
|
-
test('updates configuration', () => {
|
|
174
|
-
const originalConfig = lock.getConfig();
|
|
175
|
-
|
|
176
|
-
lock.updateConfig({
|
|
177
|
-
enabled: false,
|
|
178
|
-
lockTimeout: 10000,
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const newConfig = lock.getConfig();
|
|
182
|
-
expect(newConfig.enabled).toBe(false);
|
|
183
|
-
expect(newConfig.lockTimeout).toBe(10000);
|
|
184
|
-
// Other values should remain
|
|
185
|
-
expect(newConfig.lockKeyPrefix).toBe(originalConfig.lockKeyPrefix);
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe('isHeld()', () => {
|
|
190
|
-
test('returns false for never acquired task', () => {
|
|
191
|
-
expect(lock.isHeld('unknown-task')).toBe(false);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test('returns true for acquired task', async () => {
|
|
195
|
-
await lock.tryAcquire('held-task');
|
|
196
|
-
expect(lock.isHeld('held-task')).toBe(true);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('returns false after release', async () => {
|
|
200
|
-
await lock.tryAcquire('was-held-task');
|
|
201
|
-
expect(lock.isHeld('was-held-task')).toBe(true);
|
|
202
|
-
|
|
203
|
-
await lock.release('was-held-task');
|
|
204
|
-
expect(lock.isHeld('was-held-task')).toBe(false);
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
describe('lock key generation', () => {
|
|
209
|
-
test('generates positive bigint keys', async () => {
|
|
210
|
-
const tasks = ['task-1', 'task-2', 'TaskWithCaps', 'task.with.dots', 'task-with-dashes'];
|
|
211
|
-
|
|
212
|
-
for (const task of tasks) {
|
|
213
|
-
const result = await lock.tryAcquire(task);
|
|
214
|
-
expect(result.lockKey).toBeGreaterThan(0n);
|
|
215
|
-
await lock.release(task);
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test('handles empty string task id', async () => {
|
|
220
|
-
const result = await lock.tryAcquire('');
|
|
221
|
-
expect(result.lockKey).toBeDefined();
|
|
222
|
-
await lock.release('');
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
test('handles unicode task ids', async () => {
|
|
226
|
-
const result = await lock.tryAcquire('task-日本語');
|
|
227
|
-
expect(result.acquired).toBe(true);
|
|
228
|
-
expect(result.lockKey).toBeGreaterThan(0n);
|
|
229
|
-
await lock.release('task-日本語');
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test('handles very long task ids', async () => {
|
|
233
|
-
const longId = 'a'.repeat(1000);
|
|
234
|
-
const result = await lock.tryAcquire(longId);
|
|
235
|
-
expect(result.acquired).toBe(true);
|
|
236
|
-
expect(result.lockKey).toBeGreaterThan(0n);
|
|
237
|
-
await lock.release(longId);
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
describe('DistributedLock with timeout', () => {
|
|
243
|
-
let lock: DistributedLock;
|
|
244
|
-
|
|
245
|
-
beforeEach(() => {
|
|
246
|
-
resetDistributedLock();
|
|
247
|
-
lock = new DistributedLock({
|
|
248
|
-
enabled: true,
|
|
249
|
-
enableLogging: false,
|
|
250
|
-
lockTimeout: 200, // 200ms timeout
|
|
251
|
-
retryInterval: 50,
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
afterEach(async () => {
|
|
256
|
-
await lock.releaseAll();
|
|
257
|
-
resetDistributedLock();
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test('retries until timeout when lock not available', async () => {
|
|
261
|
-
// This test verifies the retry mechanism is called
|
|
262
|
-
// In a real multi-instance scenario, we'd test across database sessions
|
|
263
|
-
|
|
264
|
-
const startTime = Date.now();
|
|
265
|
-
|
|
266
|
-
// First acquire the lock
|
|
267
|
-
const result1 = await lock.tryAcquire('timeout-task');
|
|
268
|
-
expect(result1.acquired).toBe(true);
|
|
269
|
-
|
|
270
|
-
const elapsed = Date.now() - startTime;
|
|
271
|
-
// Should return quickly when lock is available
|
|
272
|
-
expect(elapsed).toBeLessThan(100);
|
|
273
|
-
});
|
|
274
|
-
});
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for SchedulerManager time-based (entity-less) tasks.
|
|
3
|
-
* Covers BUNSANE-002: @ScheduledTask without query/componentTarget.
|
|
4
|
-
*/
|
|
5
|
-
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
6
|
-
import { SchedulerManager } from '../../../core/SchedulerManager';
|
|
7
|
-
import { ScheduleInterval } from '../../../types/scheduler.types';
|
|
8
|
-
|
|
9
|
-
describe('SchedulerManager time-based tasks', () => {
|
|
10
|
-
let scheduler: SchedulerManager;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
scheduler = SchedulerManager.getInstance();
|
|
14
|
-
scheduler.updateConfig({
|
|
15
|
-
enabled: true,
|
|
16
|
-
enableLogging: false,
|
|
17
|
-
runOnStart: false,
|
|
18
|
-
distributedLocking: false,
|
|
19
|
-
maxConcurrentTasks: 5,
|
|
20
|
-
defaultTimeout: 5000,
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterEach(async () => {
|
|
25
|
-
await scheduler.stop().catch(() => {});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('registers task with no query / no componentTarget', () => {
|
|
29
|
-
let called = 0;
|
|
30
|
-
const service = {
|
|
31
|
-
tick: async () => {
|
|
32
|
-
called++;
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
expect(() =>
|
|
37
|
-
scheduler.registerTask({
|
|
38
|
-
id: 'test.timebased.register',
|
|
39
|
-
name: 'timebased-register',
|
|
40
|
-
interval: ScheduleInterval.MINUTE,
|
|
41
|
-
options: {},
|
|
42
|
-
service,
|
|
43
|
-
methodName: 'tick',
|
|
44
|
-
nextExecution: new Date(),
|
|
45
|
-
executionCount: 0,
|
|
46
|
-
isRunning: false,
|
|
47
|
-
enabled: true,
|
|
48
|
-
})
|
|
49
|
-
).not.toThrow();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('executes handler with no entity argument', async () => {
|
|
53
|
-
const receivedArgsBox: { args: unknown[] | null } = { args: null };
|
|
54
|
-
const service = {
|
|
55
|
-
tick: async (...args: unknown[]) => {
|
|
56
|
-
receivedArgsBox.args = args;
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
scheduler.registerTask({
|
|
61
|
-
id: 'test.timebased.exec',
|
|
62
|
-
name: 'timebased-exec',
|
|
63
|
-
interval: ScheduleInterval.MINUTE,
|
|
64
|
-
options: {},
|
|
65
|
-
service,
|
|
66
|
-
methodName: 'tick',
|
|
67
|
-
nextExecution: new Date(),
|
|
68
|
-
executionCount: 0,
|
|
69
|
-
isRunning: false,
|
|
70
|
-
enabled: true,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const ok = await scheduler.executeTaskNow('test.timebased.exec');
|
|
74
|
-
expect(ok).toBe(true);
|
|
75
|
-
expect(receivedArgsBox.args).toEqual([]);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('rejects task still missing required fields', () => {
|
|
79
|
-
const service = { tick: async () => {} };
|
|
80
|
-
expect(() =>
|
|
81
|
-
scheduler.registerTask({
|
|
82
|
-
// missing id
|
|
83
|
-
name: 'bad',
|
|
84
|
-
interval: ScheduleInterval.MINUTE,
|
|
85
|
-
options: {},
|
|
86
|
-
service,
|
|
87
|
-
methodName: 'tick',
|
|
88
|
-
nextExecution: new Date(),
|
|
89
|
-
executionCount: 0,
|
|
90
|
-
isRunning: false,
|
|
91
|
-
enabled: true,
|
|
92
|
-
} as any)
|
|
93
|
-
).toThrow(/missing required fields/);
|
|
94
|
-
});
|
|
95
|
-
});
|