@stacksjs/ts-cloud-core 0.1.1
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/LICENSE.md +21 -0
- package/README.md +321 -0
- package/package.json +31 -0
- package/src/advanced-features.test.ts +465 -0
- package/src/aws/cloudformation.ts +421 -0
- package/src/aws/cloudfront.ts +158 -0
- package/src/aws/credentials.test.ts +132 -0
- package/src/aws/credentials.ts +545 -0
- package/src/aws/index.ts +87 -0
- package/src/aws/s3.test.ts +188 -0
- package/src/aws/s3.ts +1088 -0
- package/src/aws/signature.test.ts +670 -0
- package/src/aws/signature.ts +1155 -0
- package/src/backup/disaster-recovery.test.ts +726 -0
- package/src/backup/disaster-recovery.ts +500 -0
- package/src/backup/index.ts +34 -0
- package/src/backup/manager.test.ts +498 -0
- package/src/backup/manager.ts +432 -0
- package/src/cicd/circleci.ts +430 -0
- package/src/cicd/github-actions.ts +424 -0
- package/src/cicd/gitlab-ci.ts +255 -0
- package/src/cicd/index.ts +8 -0
- package/src/cli/history.ts +396 -0
- package/src/cli/index.ts +10 -0
- package/src/cli/progress.ts +458 -0
- package/src/cli/repl.ts +454 -0
- package/src/cli/suggestions.ts +327 -0
- package/src/cli/table.test.ts +319 -0
- package/src/cli/table.ts +332 -0
- package/src/cloudformation/builder.test.ts +327 -0
- package/src/cloudformation/builder.ts +378 -0
- package/src/cloudformation/builders/api-gateway.ts +449 -0
- package/src/cloudformation/builders/cache.ts +334 -0
- package/src/cloudformation/builders/cdn.ts +278 -0
- package/src/cloudformation/builders/compute.ts +485 -0
- package/src/cloudformation/builders/database.ts +392 -0
- package/src/cloudformation/builders/functions.ts +343 -0
- package/src/cloudformation/builders/messaging.ts +140 -0
- package/src/cloudformation/builders/monitoring.ts +300 -0
- package/src/cloudformation/builders/network.ts +264 -0
- package/src/cloudformation/builders/queue.ts +147 -0
- package/src/cloudformation/builders/security.ts +399 -0
- package/src/cloudformation/builders/storage.ts +285 -0
- package/src/cloudformation/index.ts +30 -0
- package/src/cloudformation/types.ts +173 -0
- package/src/compliance/aws-config.ts +543 -0
- package/src/compliance/cloudtrail.ts +376 -0
- package/src/compliance/compliance.test.ts +423 -0
- package/src/compliance/guardduty.ts +446 -0
- package/src/compliance/index.ts +66 -0
- package/src/compliance/security-hub.ts +456 -0
- package/src/containers/build-optimization.ts +416 -0
- package/src/containers/containers.test.ts +508 -0
- package/src/containers/image-scanning.ts +360 -0
- package/src/containers/index.ts +9 -0
- package/src/containers/registry.ts +293 -0
- package/src/containers/service-mesh.ts +520 -0
- package/src/database/database.test.ts +762 -0
- package/src/database/index.ts +9 -0
- package/src/database/migrations.ts +444 -0
- package/src/database/performance.ts +528 -0
- package/src/database/replicas.ts +534 -0
- package/src/database/users.ts +494 -0
- package/src/dependency-graph.ts +143 -0
- package/src/deployment/ab-testing.ts +582 -0
- package/src/deployment/blue-green.ts +452 -0
- package/src/deployment/canary.ts +500 -0
- package/src/deployment/deployment.test.ts +526 -0
- package/src/deployment/index.ts +61 -0
- package/src/deployment/progressive.ts +62 -0
- package/src/dns/dns.test.ts +641 -0
- package/src/dns/dnssec.ts +315 -0
- package/src/dns/index.ts +8 -0
- package/src/dns/resolver.ts +496 -0
- package/src/dns/routing.ts +593 -0
- package/src/email/advanced/analytics.ts +445 -0
- package/src/email/advanced/index.ts +11 -0
- package/src/email/advanced/rules.ts +465 -0
- package/src/email/advanced/scheduling.ts +352 -0
- package/src/email/advanced/search.ts +412 -0
- package/src/email/advanced/shared-mailboxes.ts +404 -0
- package/src/email/advanced/templates.ts +455 -0
- package/src/email/advanced/threading.ts +281 -0
- package/src/email/analytics.ts +467 -0
- package/src/email/bounce-handling.ts +425 -0
- package/src/email/email.test.ts +431 -0
- package/src/email/handlers/__tests__/inbound.test.ts +38 -0
- package/src/email/handlers/__tests__/outbound.test.ts +37 -0
- package/src/email/handlers/converter.ts +227 -0
- package/src/email/handlers/feedback.ts +228 -0
- package/src/email/handlers/inbound.ts +169 -0
- package/src/email/handlers/outbound.ts +178 -0
- package/src/email/index.ts +15 -0
- package/src/email/reputation.ts +303 -0
- package/src/email/templates.ts +352 -0
- package/src/errors/index.test.ts +434 -0
- package/src/errors/index.ts +416 -0
- package/src/health-checks/index.ts +40 -0
- package/src/index.ts +360 -0
- package/src/intrinsic-functions.ts +118 -0
- package/src/lambda/concurrency.ts +330 -0
- package/src/lambda/destinations.ts +345 -0
- package/src/lambda/dlq.ts +425 -0
- package/src/lambda/index.ts +11 -0
- package/src/lambda/lambda.test.ts +840 -0
- package/src/lambda/layers.ts +263 -0
- package/src/lambda/versions.ts +376 -0
- package/src/lambda/vpc.ts +399 -0
- package/src/local/config.ts +114 -0
- package/src/local/index.ts +6 -0
- package/src/local/mock-aws.ts +351 -0
- package/src/modules/ai.ts +340 -0
- package/src/modules/api.ts +478 -0
- package/src/modules/auth.ts +805 -0
- package/src/modules/cache.ts +417 -0
- package/src/modules/cdn.ts +1062 -0
- package/src/modules/communication.ts +1094 -0
- package/src/modules/compute.ts +3348 -0
- package/src/modules/database.ts +554 -0
- package/src/modules/deployment.ts +1079 -0
- package/src/modules/dns.ts +337 -0
- package/src/modules/email.ts +1538 -0
- package/src/modules/filesystem.ts +515 -0
- package/src/modules/index.ts +32 -0
- package/src/modules/messaging.ts +486 -0
- package/src/modules/monitoring.ts +2086 -0
- package/src/modules/network.ts +664 -0
- package/src/modules/parameter-store.ts +325 -0
- package/src/modules/permissions.ts +1081 -0
- package/src/modules/phone.ts +494 -0
- package/src/modules/queue.ts +1260 -0
- package/src/modules/redirects.ts +464 -0
- package/src/modules/registry.ts +699 -0
- package/src/modules/search.ts +401 -0
- package/src/modules/secrets.ts +416 -0
- package/src/modules/security.ts +731 -0
- package/src/modules/sms.ts +389 -0
- package/src/modules/storage.ts +1120 -0
- package/src/modules/workflow.ts +680 -0
- package/src/multi-account/config.ts +521 -0
- package/src/multi-account/index.ts +7 -0
- package/src/multi-account/manager.ts +427 -0
- package/src/multi-region/cross-region.ts +410 -0
- package/src/multi-region/index.ts +8 -0
- package/src/multi-region/manager.ts +483 -0
- package/src/multi-region/regions.ts +435 -0
- package/src/network-security/index.ts +48 -0
- package/src/observability/index.ts +9 -0
- package/src/observability/logs.ts +522 -0
- package/src/observability/metrics.ts +460 -0
- package/src/observability/observability.test.ts +782 -0
- package/src/observability/synthetics.ts +568 -0
- package/src/observability/xray.ts +358 -0
- package/src/phone/advanced/analytics.ts +349 -0
- package/src/phone/advanced/callbacks.ts +428 -0
- package/src/phone/advanced/index.ts +8 -0
- package/src/phone/advanced/ivr-builder.ts +504 -0
- package/src/phone/advanced/recording.ts +310 -0
- package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
- package/src/phone/handlers/incoming-call.ts +117 -0
- package/src/phone/handlers/missed-call.ts +116 -0
- package/src/phone/handlers/voicemail.ts +179 -0
- package/src/phone/index.ts +9 -0
- package/src/presets/api-backend.ts +134 -0
- package/src/presets/data-pipeline.ts +204 -0
- package/src/presets/extend.test.ts +295 -0
- package/src/presets/extend.ts +297 -0
- package/src/presets/fullstack-app.ts +144 -0
- package/src/presets/index.ts +27 -0
- package/src/presets/jamstack.ts +135 -0
- package/src/presets/microservices.ts +167 -0
- package/src/presets/ml-api.ts +208 -0
- package/src/presets/nodejs-server.ts +104 -0
- package/src/presets/nodejs-serverless.ts +114 -0
- package/src/presets/realtime-app.ts +184 -0
- package/src/presets/static-site.ts +64 -0
- package/src/presets/traditional-web-app.ts +339 -0
- package/src/presets/wordpress.ts +138 -0
- package/src/preview/github.test.ts +249 -0
- package/src/preview/github.ts +297 -0
- package/src/preview/index.ts +37 -0
- package/src/preview/manager.test.ts +440 -0
- package/src/preview/manager.ts +326 -0
- package/src/preview/notifications.test.ts +582 -0
- package/src/preview/notifications.ts +341 -0
- package/src/queue/batch-processing.ts +402 -0
- package/src/queue/dlq-monitoring.ts +402 -0
- package/src/queue/fifo.ts +342 -0
- package/src/queue/index.ts +9 -0
- package/src/queue/management.ts +428 -0
- package/src/queue/queue.test.ts +429 -0
- package/src/resource-mgmt/index.ts +39 -0
- package/src/resource-naming.ts +62 -0
- package/src/s3/index.ts +523 -0
- package/src/schema/cloud-config.schema.json +554 -0
- package/src/schema/index.ts +68 -0
- package/src/security/certificate-manager.ts +492 -0
- package/src/security/index.ts +9 -0
- package/src/security/scanning.ts +545 -0
- package/src/security/secrets-manager.ts +476 -0
- package/src/security/secrets-rotation.ts +456 -0
- package/src/security/security.test.ts +738 -0
- package/src/sms/advanced/ab-testing.ts +389 -0
- package/src/sms/advanced/analytics.ts +336 -0
- package/src/sms/advanced/campaigns.ts +523 -0
- package/src/sms/advanced/chatbot.ts +224 -0
- package/src/sms/advanced/index.ts +10 -0
- package/src/sms/advanced/link-tracking.ts +248 -0
- package/src/sms/advanced/mms.ts +308 -0
- package/src/sms/handlers/__tests__/send.test.ts +40 -0
- package/src/sms/handlers/delivery-status.ts +133 -0
- package/src/sms/handlers/receive.ts +162 -0
- package/src/sms/handlers/send.ts +174 -0
- package/src/sms/index.ts +9 -0
- package/src/stack-diff.ts +389 -0
- package/src/static-site/index.ts +85 -0
- package/src/template-builder.ts +110 -0
- package/src/template-validator.ts +574 -0
- package/src/utils/cache.ts +291 -0
- package/src/utils/diff.ts +269 -0
- package/src/utils/hash.ts +227 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/parallel.ts +294 -0
- package/src/validators/credentials.test.ts +274 -0
- package/src/validators/credentials.ts +233 -0
- package/src/validators/quotas.test.ts +434 -0
- package/src/validators/quotas.ts +217 -0
- package/test/ai.test.ts +327 -0
- package/test/api.test.ts +511 -0
- package/test/auth.test.ts +632 -0
- package/test/cache.test.ts +406 -0
- package/test/cdn.test.ts +247 -0
- package/test/compute.test.ts +861 -0
- package/test/database.test.ts +523 -0
- package/test/deployment.test.ts +499 -0
- package/test/dns.test.ts +270 -0
- package/test/email.test.ts +439 -0
- package/test/filesystem.test.ts +382 -0
- package/test/integration.test.ts +350 -0
- package/test/messaging.test.ts +514 -0
- package/test/monitoring.test.ts +634 -0
- package/test/network.test.ts +425 -0
- package/test/permissions.test.ts +488 -0
- package/test/queue.test.ts +484 -0
- package/test/registry.test.ts +306 -0
- package/test/security.test.ts +462 -0
- package/test/storage.test.ts +463 -0
- package/test/template-validator.test.ts +559 -0
- package/test/workflow.test.ts +592 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
MigrationManager,
|
|
4
|
+
migrationManager,
|
|
5
|
+
ReplicaManager,
|
|
6
|
+
replicaManager,
|
|
7
|
+
PerformanceManager,
|
|
8
|
+
performanceManager,
|
|
9
|
+
DatabaseUserManager,
|
|
10
|
+
databaseUserManager,
|
|
11
|
+
} from '.'
|
|
12
|
+
|
|
13
|
+
describe('Migration Manager', () => {
|
|
14
|
+
let manager: MigrationManager
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
manager = new MigrationManager()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('Migration Creation', () => {
|
|
21
|
+
it('should create migration plan', () => {
|
|
22
|
+
const plan = manager.createPlan({
|
|
23
|
+
name: 'Initial Schema',
|
|
24
|
+
database: {
|
|
25
|
+
type: 'rds',
|
|
26
|
+
identifier: 'my-database',
|
|
27
|
+
engine: 'postgres',
|
|
28
|
+
},
|
|
29
|
+
migrations: [],
|
|
30
|
+
autoApply: false,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
expect(plan.id).toContain('migration-plan')
|
|
34
|
+
expect(plan.name).toBe('Initial Schema')
|
|
35
|
+
expect(plan.database.type).toBe('rds')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should create schema migration', () => {
|
|
39
|
+
const migration = manager.createSchemaMigration({
|
|
40
|
+
version: '1.0.0',
|
|
41
|
+
name: 'Add user table',
|
|
42
|
+
tableName: 'users',
|
|
43
|
+
changes: [
|
|
44
|
+
{
|
|
45
|
+
type: 'add_column',
|
|
46
|
+
columnName: 'email',
|
|
47
|
+
columnType: 'VARCHAR(255)',
|
|
48
|
+
nullable: false,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
expect(migration.id).toContain('migration')
|
|
54
|
+
expect(migration.version).toBe('1.0.0')
|
|
55
|
+
expect(migration.up).toContain('ADD COLUMN')
|
|
56
|
+
expect(migration.down).toContain('DROP COLUMN')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should create data migration', () => {
|
|
60
|
+
const migration = manager.createDataMigration({
|
|
61
|
+
version: '1.0.1',
|
|
62
|
+
name: 'Seed initial data',
|
|
63
|
+
upSQL: "INSERT INTO users (name) VALUES ('Admin');",
|
|
64
|
+
downSQL: "DELETE FROM users WHERE name = 'Admin';",
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
expect(migration.version).toBe('1.0.1')
|
|
68
|
+
expect(migration.up).toContain('INSERT')
|
|
69
|
+
expect(migration.down).toContain('DELETE')
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe('Migration Execution', () => {
|
|
74
|
+
it('should execute migration plan in dry-run mode', async () => {
|
|
75
|
+
const plan = manager.createPlan({
|
|
76
|
+
name: 'Test Plan',
|
|
77
|
+
database: {
|
|
78
|
+
type: 'rds',
|
|
79
|
+
identifier: 'test-db',
|
|
80
|
+
},
|
|
81
|
+
migrations: [],
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const migration = manager.createDataMigration({
|
|
85
|
+
version: '1.0.0',
|
|
86
|
+
name: 'Test migration',
|
|
87
|
+
upSQL: 'CREATE TABLE test (id INT);',
|
|
88
|
+
downSQL: 'DROP TABLE test;',
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
manager.addMigrationToPlan(plan.id, migration)
|
|
92
|
+
|
|
93
|
+
const result = await manager.executePlan(plan.id, true)
|
|
94
|
+
|
|
95
|
+
expect(result.success).toBe(true)
|
|
96
|
+
expect(result.appliedMigrations).toHaveLength(1)
|
|
97
|
+
expect(result.failedMigrations).toHaveLength(0)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('should get migration status', () => {
|
|
101
|
+
const plan = manager.createPlan({
|
|
102
|
+
name: 'Test Plan',
|
|
103
|
+
database: { type: 'rds', identifier: 'test-db' },
|
|
104
|
+
migrations: [],
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const status = manager.getMigrationStatus(plan.id)
|
|
108
|
+
|
|
109
|
+
expect(status.currentVersion).toBe('0.0.0')
|
|
110
|
+
expect(status.pendingMigrations).toHaveLength(0)
|
|
111
|
+
expect(status.appliedMigrations).toHaveLength(0)
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe('Migration Validation', () => {
|
|
116
|
+
it('should validate migration plan', () => {
|
|
117
|
+
const plan = manager.createPlan({
|
|
118
|
+
name: 'Test Plan',
|
|
119
|
+
database: { type: 'rds', identifier: 'test-db' },
|
|
120
|
+
migrations: [],
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const migration = manager.createDataMigration({
|
|
124
|
+
version: '1.0.0',
|
|
125
|
+
name: 'Test',
|
|
126
|
+
upSQL: 'SELECT 1;',
|
|
127
|
+
downSQL: 'SELECT 0;',
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
manager.addMigrationToPlan(plan.id, migration)
|
|
131
|
+
|
|
132
|
+
const validation = manager.validatePlan(plan.id)
|
|
133
|
+
|
|
134
|
+
expect(validation.valid).toBe(true)
|
|
135
|
+
expect(validation.errors).toHaveLength(0)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('should detect duplicate migration versions', () => {
|
|
139
|
+
const plan = manager.createPlan({
|
|
140
|
+
name: 'Test Plan',
|
|
141
|
+
database: { type: 'rds', identifier: 'test-db' },
|
|
142
|
+
migrations: [],
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const migration1 = manager.createDataMigration({
|
|
146
|
+
version: '1.0.0',
|
|
147
|
+
name: 'First',
|
|
148
|
+
upSQL: 'SELECT 1;',
|
|
149
|
+
downSQL: '',
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const migration2 = manager.createDataMigration({
|
|
153
|
+
version: '1.0.0',
|
|
154
|
+
name: 'Second',
|
|
155
|
+
upSQL: 'SELECT 2;',
|
|
156
|
+
downSQL: '',
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
manager.addMigrationToPlan(plan.id, migration1)
|
|
160
|
+
manager.addMigrationToPlan(plan.id, migration2)
|
|
161
|
+
|
|
162
|
+
const validation = manager.validatePlan(plan.id)
|
|
163
|
+
|
|
164
|
+
expect(validation.valid).toBe(false)
|
|
165
|
+
expect(validation.errors.some(e => e.includes('Duplicate'))).toBe(true)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('should use global instance', () => {
|
|
170
|
+
expect(migrationManager).toBeInstanceOf(MigrationManager)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
describe('Replica Manager', () => {
|
|
175
|
+
let manager: ReplicaManager
|
|
176
|
+
|
|
177
|
+
beforeEach(() => {
|
|
178
|
+
manager = new ReplicaManager()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
describe('Replica Creation', () => {
|
|
182
|
+
it('should create read replica', () => {
|
|
183
|
+
const replica = manager.createRDSReplica({
|
|
184
|
+
sourceDatabase: 'primary-db',
|
|
185
|
+
name: 'replica-1',
|
|
186
|
+
region: 'us-east-1',
|
|
187
|
+
instanceClass: 'db.t3.medium',
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
expect(replica.id).toContain('replica')
|
|
191
|
+
expect(replica.name).toBe('replica-1')
|
|
192
|
+
expect(replica.sourceDatabase).toBe('primary-db')
|
|
193
|
+
expect(replica.status).toBe('creating')
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('should create cross-region replica', () => {
|
|
197
|
+
const replica = manager.createCrossRegionReplica({
|
|
198
|
+
sourceDatabase: 'primary-db',
|
|
199
|
+
name: 'replica-eu',
|
|
200
|
+
targetRegion: 'eu-west-1',
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
expect(replica.region).toBe('eu-west-1')
|
|
204
|
+
expect(replica.multiAZ).toBe(true)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('should create replication group with auto-scaling', () => {
|
|
208
|
+
const group = manager.createAutoScalingReplicationGroup({
|
|
209
|
+
name: 'production-replicas',
|
|
210
|
+
primaryDatabase: 'primary-db',
|
|
211
|
+
minReplicas: 2,
|
|
212
|
+
maxReplicas: 5,
|
|
213
|
+
targetCPU: 70,
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
expect(group.id).toContain('replication-group')
|
|
217
|
+
expect(group.autoScaling?.enabled).toBe(true)
|
|
218
|
+
expect(group.autoScaling?.minReplicas).toBe(2)
|
|
219
|
+
expect(group.autoScaling?.maxReplicas).toBe(5)
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe('RDS Proxy', () => {
|
|
224
|
+
it('should create connection pool proxy', () => {
|
|
225
|
+
const proxy = manager.createConnectionPoolProxy({
|
|
226
|
+
name: 'app-proxy',
|
|
227
|
+
engineFamily: 'POSTGRESQL',
|
|
228
|
+
targetDatabase: 'my-database',
|
|
229
|
+
vpcSubnetIds: ['subnet-1', 'subnet-2'],
|
|
230
|
+
securityGroupIds: ['sg-1'],
|
|
231
|
+
secretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:db-creds',
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
expect(proxy.id).toContain('rds-proxy')
|
|
235
|
+
expect(proxy.name).toBe('app-proxy')
|
|
236
|
+
expect(proxy.engineFamily).toBe('POSTGRESQL')
|
|
237
|
+
expect(proxy.requireTLS).toBe(true)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('should create serverless proxy with optimized settings', () => {
|
|
241
|
+
const proxy = manager.createServerlessProxy({
|
|
242
|
+
name: 'lambda-proxy',
|
|
243
|
+
engineFamily: 'MYSQL',
|
|
244
|
+
targetDatabase: 'serverless-db',
|
|
245
|
+
vpcSubnetIds: ['subnet-1'],
|
|
246
|
+
securityGroupIds: ['sg-1'],
|
|
247
|
+
secretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:db-creds',
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
expect(proxy.maxIdleConnectionsPercent).toBe(10)
|
|
251
|
+
expect(proxy.connectionBorrowTimeout).toBe(60)
|
|
252
|
+
expect(proxy.idleClientTimeout).toBe(300)
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
describe('Replica Operations', () => {
|
|
257
|
+
it('should promote replica to primary', () => {
|
|
258
|
+
const replica = manager.createRDSReplica({
|
|
259
|
+
sourceDatabase: 'primary-db',
|
|
260
|
+
name: 'replica-1',
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
replica.replicationLag = 100 // Low lag
|
|
264
|
+
|
|
265
|
+
const result = manager.promoteReplica(replica.id)
|
|
266
|
+
|
|
267
|
+
expect(result.success).toBe(true)
|
|
268
|
+
expect(result.message).toContain('successfully')
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('should fail promotion with high replication lag', () => {
|
|
272
|
+
const replica = manager.createRDSReplica({
|
|
273
|
+
sourceDatabase: 'primary-db',
|
|
274
|
+
name: 'replica-1',
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
replica.replicationLag = 10000 // High lag
|
|
278
|
+
|
|
279
|
+
const result = manager.promoteReplica(replica.id)
|
|
280
|
+
|
|
281
|
+
expect(result.success).toBe(false)
|
|
282
|
+
expect(result.message).toContain('lag too high')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('should get replication lag', () => {
|
|
286
|
+
const replica = manager.createRDSReplica({
|
|
287
|
+
sourceDatabase: 'primary-db',
|
|
288
|
+
name: 'replica-1',
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
const lag = manager.getReplicationLag(replica.id)
|
|
292
|
+
|
|
293
|
+
expect(typeof lag).toBe('number')
|
|
294
|
+
expect(lag).toBeGreaterThanOrEqual(0)
|
|
295
|
+
})
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
describe('CloudFormation Generation', () => {
|
|
299
|
+
it('should generate replica CloudFormation', () => {
|
|
300
|
+
const replica = manager.createRDSReplica({
|
|
301
|
+
sourceDatabase: 'primary-db',
|
|
302
|
+
name: 'replica-1',
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
const cf = manager.generateReplicaCF(replica)
|
|
306
|
+
|
|
307
|
+
expect(cf.Type).toBe('AWS::RDS::DBInstance')
|
|
308
|
+
expect(cf.Properties.SourceDBInstanceIdentifier).toBe('primary-db')
|
|
309
|
+
expect(cf.Properties.DBInstanceIdentifier).toBe('replica-1')
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it('should generate proxy CloudFormation', () => {
|
|
313
|
+
const proxy = manager.createConnectionPoolProxy({
|
|
314
|
+
name: 'test-proxy',
|
|
315
|
+
engineFamily: 'POSTGRESQL',
|
|
316
|
+
targetDatabase: 'test-db',
|
|
317
|
+
vpcSubnetIds: ['subnet-1'],
|
|
318
|
+
securityGroupIds: ['sg-1'],
|
|
319
|
+
secretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:db-creds',
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
const cf = manager.generateProxyCF(proxy)
|
|
323
|
+
|
|
324
|
+
expect(cf.Type).toBe('AWS::RDS::DBProxy')
|
|
325
|
+
expect(cf.Properties.DBProxyName).toBe('test-proxy')
|
|
326
|
+
expect(cf.Properties.EngineFamily).toBe('POSTGRESQL')
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
it('should use global instance', () => {
|
|
331
|
+
expect(replicaManager).toBeInstanceOf(ReplicaManager)
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
describe('Performance Manager', () => {
|
|
336
|
+
let manager: PerformanceManager
|
|
337
|
+
|
|
338
|
+
beforeEach(() => {
|
|
339
|
+
manager = new PerformanceManager()
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
describe('Performance Insights', () => {
|
|
343
|
+
it('should enable performance insights', () => {
|
|
344
|
+
const insights = manager.enablePerformanceInsights({
|
|
345
|
+
name: 'production-insights',
|
|
346
|
+
databaseIdentifier: 'prod-db',
|
|
347
|
+
retentionPeriod: 7,
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
expect(insights.id).toContain('pi-')
|
|
351
|
+
expect(insights.enabled).toBe(true)
|
|
352
|
+
expect(insights.retentionPeriod).toBe(7)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
it('should enable performance insights with encryption', () => {
|
|
356
|
+
const insights = manager.enablePerformanceInsights({
|
|
357
|
+
name: 'encrypted-insights',
|
|
358
|
+
databaseIdentifier: 'prod-db',
|
|
359
|
+
kmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345',
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
expect(insights.kmsKeyId).toBe('arn:aws:kms:us-east-1:123456789012:key/12345')
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
describe('Slow Query Logging', () => {
|
|
367
|
+
it('should enable slow query log to CloudWatch', () => {
|
|
368
|
+
const log = manager.enableSlowQueryLog({
|
|
369
|
+
name: 'slow-queries',
|
|
370
|
+
databaseIdentifier: 'prod-db',
|
|
371
|
+
logDestination: 'cloudwatch',
|
|
372
|
+
cloudwatchLogGroup: '/aws/rds/slow-queries',
|
|
373
|
+
minExecutionTime: 2000,
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
expect(log.id).toContain('slow-query')
|
|
377
|
+
expect(log.logDestination).toBe('cloudwatch')
|
|
378
|
+
expect(log.minExecutionTime).toBe(2000)
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it('should enable slow query log to S3', () => {
|
|
382
|
+
const log = manager.enableSlowQueryLog({
|
|
383
|
+
name: 'slow-queries-s3',
|
|
384
|
+
databaseIdentifier: 'prod-db',
|
|
385
|
+
logDestination: 's3',
|
|
386
|
+
s3Bucket: 'my-logs-bucket',
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
expect(log.logDestination).toBe('s3')
|
|
390
|
+
expect(log.s3Bucket).toBe('my-logs-bucket')
|
|
391
|
+
expect(log.s3Prefix).toBe('slow-queries/')
|
|
392
|
+
})
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
describe('Query Metrics', () => {
|
|
396
|
+
it('should record query metric', () => {
|
|
397
|
+
const metric = manager.recordQueryMetric({
|
|
398
|
+
queryId: 'SELECT_USERS',
|
|
399
|
+
sql: 'SELECT * FROM users WHERE active = true',
|
|
400
|
+
executionCount: 100,
|
|
401
|
+
avgExecutionTime: 25,
|
|
402
|
+
maxExecutionTime: 150,
|
|
403
|
+
minExecutionTime: 10,
|
|
404
|
+
totalCPUTime: 2500,
|
|
405
|
+
totalIOWait: 500,
|
|
406
|
+
totalLockWait: 100,
|
|
407
|
+
rowsExamined: 1000,
|
|
408
|
+
rowsReturned: 100,
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
expect(metric.id).toContain('metric')
|
|
412
|
+
expect(metric.avgExecutionTime).toBe(25)
|
|
413
|
+
expect(metric.executionCount).toBe(100)
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
describe('Performance Reports', () => {
|
|
418
|
+
it('should generate daily performance report', () => {
|
|
419
|
+
const report = manager.generatePerformanceReport({
|
|
420
|
+
name: 'Daily Report',
|
|
421
|
+
databaseIdentifier: 'prod-db',
|
|
422
|
+
reportType: 'daily',
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
expect(report.id).toContain('report')
|
|
426
|
+
expect(report.reportType).toBe('daily')
|
|
427
|
+
expect(report.metrics).toBeDefined()
|
|
428
|
+
expect(report.recommendations).toBeDefined()
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
it('should include performance metrics', () => {
|
|
432
|
+
const report = manager.generatePerformanceReport({
|
|
433
|
+
name: 'Test Report',
|
|
434
|
+
databaseIdentifier: 'test-db',
|
|
435
|
+
reportType: 'weekly',
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
expect(report.metrics.avgCPU).toBeGreaterThan(0)
|
|
439
|
+
expect(report.metrics.avgConnections).toBeGreaterThan(0)
|
|
440
|
+
expect(report.metrics.cacheHitRatio).toBeGreaterThan(0)
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('should generate recommendations based on metrics', () => {
|
|
444
|
+
// Add slow query
|
|
445
|
+
manager.recordQueryMetric({
|
|
446
|
+
queryId: 'SLOW_QUERY',
|
|
447
|
+
sql: 'SELECT * FROM large_table',
|
|
448
|
+
executionCount: 10,
|
|
449
|
+
avgExecutionTime: 5000,
|
|
450
|
+
maxExecutionTime: 10000,
|
|
451
|
+
minExecutionTime: 2000,
|
|
452
|
+
totalCPUTime: 50000,
|
|
453
|
+
totalIOWait: 10000,
|
|
454
|
+
totalLockWait: 1000,
|
|
455
|
+
rowsExamined: 1000000,
|
|
456
|
+
rowsReturned: 100,
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
const report = manager.generatePerformanceReport({
|
|
460
|
+
name: 'Test Report',
|
|
461
|
+
databaseIdentifier: 'test-db',
|
|
462
|
+
reportType: 'daily',
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
expect(report.recommendations.length).toBeGreaterThan(0)
|
|
466
|
+
})
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
describe('Query Analysis', () => {
|
|
470
|
+
it('should analyze query', () => {
|
|
471
|
+
const analysis = manager.analyzeQuery('SELECT * FROM users WHERE email = "test@example.com"')
|
|
472
|
+
|
|
473
|
+
expect(analysis.id).toBeDefined()
|
|
474
|
+
expect(analysis.sql).toContain('SELECT')
|
|
475
|
+
expect(analysis.executionPlan).toBeDefined()
|
|
476
|
+
expect(analysis.bottlenecks).toBeDefined()
|
|
477
|
+
expect(analysis.recommendations).toBeDefined()
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it('should detect full table scan', () => {
|
|
481
|
+
const analysis = manager.analyzeQuery('SELECT * FROM users')
|
|
482
|
+
|
|
483
|
+
expect(analysis.bottlenecks.some(b => b.type === 'full_table_scan')).toBe(true)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
it('should recommend index', () => {
|
|
487
|
+
const recommendation = manager.recommendIndex({
|
|
488
|
+
tableName: 'users',
|
|
489
|
+
columns: ['email', 'active'],
|
|
490
|
+
reason: 'Frequently queried together in WHERE clause',
|
|
491
|
+
estimatedImprovement: 75,
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
expect(recommendation.id).toContain('index-rec')
|
|
495
|
+
expect(recommendation.createSQL).toContain('CREATE INDEX')
|
|
496
|
+
expect(recommendation.estimatedImprovement).toBe(75)
|
|
497
|
+
})
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
describe('CloudFormation Generation', () => {
|
|
501
|
+
it('should generate performance insights-cloudFormation', () => {
|
|
502
|
+
const insights = manager.enablePerformanceInsights({
|
|
503
|
+
name: 'test-insights',
|
|
504
|
+
databaseIdentifier: 'test-db',
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
const cf = manager.generatePerformanceInsightsCF(insights)
|
|
508
|
+
|
|
509
|
+
expect(cf.EnablePerformanceInsights).toBe(true)
|
|
510
|
+
expect(cf.PerformanceInsightsRetentionPeriod).toBe(7)
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
it('should generate slow query alarm', () => {
|
|
514
|
+
const cf = manager.generateSlowQueryAlarmCF({
|
|
515
|
+
alarmName: 'HighSlowQueries',
|
|
516
|
+
logGroupName: '/aws/rds/slow-queries',
|
|
517
|
+
threshold: 10,
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
expect(cf.Type).toBe('AWS::CloudWatch::Alarm')
|
|
521
|
+
expect(cf.Properties.AlarmName).toBe('HighSlowQueries')
|
|
522
|
+
expect(cf.Properties.Threshold).toBe(10)
|
|
523
|
+
})
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
it('should use global instance', () => {
|
|
527
|
+
expect(performanceManager).toBeInstanceOf(PerformanceManager)
|
|
528
|
+
})
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
describe('Database User Manager', () => {
|
|
532
|
+
let manager: DatabaseUserManager
|
|
533
|
+
|
|
534
|
+
beforeEach(() => {
|
|
535
|
+
manager = new DatabaseUserManager()
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
describe('User Creation', () => {
|
|
539
|
+
it('should create read-only user', () => {
|
|
540
|
+
const user = manager.createReadOnlyUser({
|
|
541
|
+
username: 'readonly',
|
|
542
|
+
database: 'production',
|
|
543
|
+
passwordSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:readonly',
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
expect(user.id).toContain('db-user')
|
|
547
|
+
expect(user.username).toBe('readonly')
|
|
548
|
+
expect(user.privileges).toHaveLength(1)
|
|
549
|
+
expect(user.privileges[0].privileges).toContain('SELECT')
|
|
550
|
+
expect(user.rotationEnabled).toBe(true)
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
it('should create read-write user', () => {
|
|
554
|
+
const user = manager.createReadWriteUser({
|
|
555
|
+
username: 'appuser',
|
|
556
|
+
database: 'production',
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
expect(user.privileges[0].privileges).toContain('SELECT')
|
|
560
|
+
expect(user.privileges[0].privileges).toContain('INSERT')
|
|
561
|
+
expect(user.privileges[0].privileges).toContain('UPDATE')
|
|
562
|
+
expect(user.privileges[0].privileges).toContain('DELETE')
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
it('should create admin user', () => {
|
|
566
|
+
const user = manager.createAdminUser({
|
|
567
|
+
username: 'admin',
|
|
568
|
+
database: 'production',
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
expect(user.privileges[0].privileges).toContain('ALL')
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
it('should create application user with specific tables', () => {
|
|
575
|
+
const user = manager.createApplicationUser({
|
|
576
|
+
username: 'api',
|
|
577
|
+
database: 'production',
|
|
578
|
+
tables: [
|
|
579
|
+
{ name: 'users', privileges: ['SELECT', 'INSERT', 'UPDATE'] },
|
|
580
|
+
{ name: 'sessions', privileges: ['SELECT', 'INSERT', 'DELETE'] },
|
|
581
|
+
],
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
expect(user.privileges).toHaveLength(2)
|
|
585
|
+
expect(user.privileges[0].table).toBe('users')
|
|
586
|
+
expect(user.privileges[1].table).toBe('sessions')
|
|
587
|
+
})
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
describe('Roles', () => {
|
|
591
|
+
it('should create user role', () => {
|
|
592
|
+
const role = manager.createRole({
|
|
593
|
+
name: 'Developer',
|
|
594
|
+
description: 'Developer access to test databases',
|
|
595
|
+
privileges: [
|
|
596
|
+
{
|
|
597
|
+
database: 'test',
|
|
598
|
+
privileges: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
})
|
|
602
|
+
|
|
603
|
+
expect(role.id).toContain('role')
|
|
604
|
+
expect(role.name).toBe('Developer')
|
|
605
|
+
expect(role.users).toHaveLength(0)
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
it('should assign user to role', () => {
|
|
609
|
+
const user = manager.createReadOnlyUser({
|
|
610
|
+
username: 'dev1',
|
|
611
|
+
database: 'test',
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
const role = manager.createRole({
|
|
615
|
+
name: 'Developer',
|
|
616
|
+
privileges: [
|
|
617
|
+
{
|
|
618
|
+
database: 'test',
|
|
619
|
+
privileges: ['INSERT', 'UPDATE'],
|
|
620
|
+
},
|
|
621
|
+
],
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
manager.assignUserToRole(user.id, role.id)
|
|
625
|
+
|
|
626
|
+
expect(role.users).toContain(user.id)
|
|
627
|
+
// User should now have merged privileges
|
|
628
|
+
expect(user.privileges[0].privileges).toContain('SELECT')
|
|
629
|
+
expect(user.privileges[0].privileges).toContain('INSERT')
|
|
630
|
+
})
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
describe('Privilege Management', () => {
|
|
634
|
+
it('should grant privileges', () => {
|
|
635
|
+
const user = manager.createReadOnlyUser({
|
|
636
|
+
username: 'user1',
|
|
637
|
+
database: 'production',
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
const result = manager.grantPrivileges(user.id, [
|
|
641
|
+
{
|
|
642
|
+
database: 'production',
|
|
643
|
+
privileges: ['INSERT', 'UPDATE'],
|
|
644
|
+
},
|
|
645
|
+
])
|
|
646
|
+
|
|
647
|
+
expect(result.success).toBe(true)
|
|
648
|
+
expect(user.privileges[0].privileges).toContain('INSERT')
|
|
649
|
+
expect(user.privileges[0].privileges).toContain('UPDATE')
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
it('should revoke privileges', () => {
|
|
653
|
+
const user = manager.createReadWriteUser({
|
|
654
|
+
username: 'user1',
|
|
655
|
+
database: 'production',
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
const result = manager.revokePrivileges(user.id, [
|
|
659
|
+
{
|
|
660
|
+
database: 'production',
|
|
661
|
+
privileges: ['DELETE'],
|
|
662
|
+
},
|
|
663
|
+
])
|
|
664
|
+
|
|
665
|
+
expect(result.success).toBe(true)
|
|
666
|
+
expect(user.privileges[0].privileges).not.toContain('DELETE')
|
|
667
|
+
})
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
describe('Password Rotation', () => {
|
|
671
|
+
it('should rotate password', () => {
|
|
672
|
+
const user = manager.createReadOnlyUser({
|
|
673
|
+
username: 'user1',
|
|
674
|
+
database: 'production',
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
const result = manager.rotatePassword(user.id)
|
|
678
|
+
|
|
679
|
+
expect(result.success).toBe(true)
|
|
680
|
+
expect(result.newSecretArn).toContain('arn:aws:secretsmanager')
|
|
681
|
+
expect(user.lastRotated).toBeDefined()
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
it('should check if password rotation needed', () => {
|
|
685
|
+
const user = manager.createReadOnlyUser({
|
|
686
|
+
username: 'user1',
|
|
687
|
+
database: 'production',
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
user.lastRotated = new Date(Date.now() - 100 * 24 * 60 * 60 * 1000) // 100 days ago
|
|
691
|
+
|
|
692
|
+
const needsRotation = manager.needsPasswordRotation(user.id)
|
|
693
|
+
|
|
694
|
+
expect(needsRotation).toBe(true)
|
|
695
|
+
})
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
describe('Access Auditing', () => {
|
|
699
|
+
it('should audit access', () => {
|
|
700
|
+
const audit = manager.auditAccess({
|
|
701
|
+
username: 'user1',
|
|
702
|
+
action: 'LOGIN',
|
|
703
|
+
success: true,
|
|
704
|
+
ipAddress: '192.168.1.1',
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
expect(audit.id).toContain('audit')
|
|
708
|
+
expect(audit.username).toBe('user1')
|
|
709
|
+
expect(audit.action).toBe('LOGIN')
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
it('should get user access history', () => {
|
|
713
|
+
manager.auditAccess({ username: 'user1', action: 'LOGIN', success: true })
|
|
714
|
+
manager.auditAccess({ username: 'user1', action: 'QUERY', success: true })
|
|
715
|
+
|
|
716
|
+
const history = manager.getUserAccessHistory('user1')
|
|
717
|
+
|
|
718
|
+
expect(history).toHaveLength(2)
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
it('should get failed login attempts', () => {
|
|
722
|
+
manager.auditAccess({ username: 'user1', action: 'LOGIN', success: false })
|
|
723
|
+
manager.auditAccess({ username: 'user1', action: 'LOGIN', success: false })
|
|
724
|
+
manager.auditAccess({ username: 'user1', action: 'LOGIN', success: true })
|
|
725
|
+
|
|
726
|
+
const failedAttempts = manager.getFailedLoginAttempts('user1')
|
|
727
|
+
|
|
728
|
+
expect(failedAttempts).toHaveLength(2)
|
|
729
|
+
})
|
|
730
|
+
})
|
|
731
|
+
|
|
732
|
+
describe('SQL Generation', () => {
|
|
733
|
+
it('should generate PostgreSQL user creation SQL', () => {
|
|
734
|
+
const user = manager.createReadWriteUser({
|
|
735
|
+
username: 'testuser',
|
|
736
|
+
database: 'production',
|
|
737
|
+
})
|
|
738
|
+
|
|
739
|
+
const sql = manager.generateCreateUserSQL(user, 'postgres')
|
|
740
|
+
|
|
741
|
+
expect(sql).toContain('CREATE USER testuser')
|
|
742
|
+
expect(sql).toContain('GRANT')
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
it('should generate MySQL user creation SQL', () => {
|
|
746
|
+
const user = manager.createReadOnlyUser({
|
|
747
|
+
username: 'testuser',
|
|
748
|
+
database: 'production',
|
|
749
|
+
})
|
|
750
|
+
|
|
751
|
+
const sql = manager.generateCreateUserSQL(user, 'mysql')
|
|
752
|
+
|
|
753
|
+
expect(sql).toContain("CREATE USER 'testuser'")
|
|
754
|
+
expect(sql).toContain('GRANT')
|
|
755
|
+
expect(sql).toContain('FLUSH PRIVILEGES')
|
|
756
|
+
})
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
it('should use global instance', () => {
|
|
760
|
+
expect(databaseUserManager).toBeInstanceOf(DatabaseUserManager)
|
|
761
|
+
})
|
|
762
|
+
})
|