@stravigor/core 0.1.0 → 0.2.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/package.json +1 -1
- package/src/auth/auth.ts +2 -1
- package/src/broadcast/broadcast_manager.ts +18 -5
- package/src/broadcast/client.ts +10 -4
- package/src/cache/cache_manager.ts +6 -2
- package/src/cache/http_cache.ts +1 -5
- package/src/core/container.ts +2 -6
- package/src/database/database.ts +11 -7
- package/src/database/migration/runner.ts +3 -1
- package/src/database/query_builder.ts +553 -60
- package/src/encryption/encryption_manager.ts +7 -1
- package/src/exceptions/errors.ts +1 -5
- package/src/exceptions/http_exception.ts +4 -1
- package/src/generators/api_generator.ts +8 -1
- package/src/generators/doc_generator.ts +33 -28
- package/src/generators/model_generator.ts +3 -1
- package/src/generators/test_generator.ts +81 -91
- package/src/i18n/helpers.ts +5 -1
- package/src/i18n/i18n_manager.ts +3 -1
- package/src/i18n/middleware.ts +2 -8
- package/src/mail/helpers.ts +1 -1
- package/src/mail/index.ts +4 -0
- package/src/mail/mail_manager.ts +20 -1
- package/src/mail/transports/alibaba_transport.ts +88 -0
- package/src/mail/transports/mailgun_transport.ts +74 -0
- package/src/mail/transports/resend_transport.ts +3 -4
- package/src/mail/transports/sendgrid_transport.ts +12 -9
- package/src/mail/transports/smtp_transport.ts +5 -5
- package/src/mail/types.ts +19 -1
- package/src/notification/channels/discord_channel.ts +6 -1
- package/src/notification/channels/webhook_channel.ts +8 -3
- package/src/notification/helpers.ts +7 -7
- package/src/notification/notification_manager.ts +7 -6
- package/src/orm/base_model.ts +4 -2
- package/src/queue/queue.ts +3 -1
- package/src/scheduler/cron.ts +12 -6
- package/src/scheduler/schedule.ts +17 -8
- package/src/session/session_manager.ts +3 -1
- package/src/storage/storage_manager.ts +3 -1
- package/src/view/compiler.ts +1 -3
- package/src/view/islands/island_builder.ts +4 -4
- package/src/view/islands/vue_plugin.ts +11 -15
- package/src/view/tokenizer.ts +11 -1
|
@@ -89,9 +89,7 @@ export default class TestGenerator {
|
|
|
89
89
|
const routesImport = relativeImport(this.paths.tests, this.paths.routes)
|
|
90
90
|
const userSchema = this.findUserEntity()
|
|
91
91
|
const userPkName = userSchema ? this.findSchemaPK(userSchema.name) : 'id'
|
|
92
|
-
const userPkIsUuid = userSchema
|
|
93
|
-
? this.getPkType(userSchema) === 'uuid'
|
|
94
|
-
: true
|
|
92
|
+
const userPkIsUuid = userSchema ? this.getPkType(userSchema) === 'uuid' : true
|
|
95
93
|
|
|
96
94
|
const lines: string[] = [
|
|
97
95
|
'// Generated by Strav — DO NOT EDIT',
|
|
@@ -129,7 +127,9 @@ export default class TestGenerator {
|
|
|
129
127
|
if (userSchema) {
|
|
130
128
|
const modelImport = relativeImport(this.paths.tests, this.paths.models)
|
|
131
129
|
const modelFile = toSnakeCase(userSchema.name)
|
|
132
|
-
lines.push(
|
|
130
|
+
lines.push(
|
|
131
|
+
` const { default: ${toPascalCase(userSchema.name)} } = await import('${modelImport}/${modelFile}')`
|
|
132
|
+
)
|
|
133
133
|
lines.push(` return ${toPascalCase(userSchema.name)}.find(id as string)`)
|
|
134
134
|
} else {
|
|
135
135
|
lines.push(` return null`)
|
|
@@ -210,7 +210,7 @@ export default class TestGenerator {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
lines.push('')
|
|
213
|
-
lines.push(
|
|
213
|
+
lines.push(" const result = await AccessToken.create(testUserPid, 'test-token')")
|
|
214
214
|
lines.push(' token = result.token')
|
|
215
215
|
lines.push('}')
|
|
216
216
|
lines.push('')
|
|
@@ -416,7 +416,7 @@ export default class TestGenerator {
|
|
|
416
416
|
` const response = routerInstance.handle(`,
|
|
417
417
|
` new Request('${this.requestBaseUrl()}${testPath}', {`,
|
|
418
418
|
` headers: {`,
|
|
419
|
-
` Authorization: 'NotBearer some_token'
|
|
419
|
+
` Authorization: 'NotBearer some_token',`
|
|
420
420
|
)
|
|
421
421
|
|
|
422
422
|
if (this.apiConfig.routing === ApiRouting.Subdomain) {
|
|
@@ -431,7 +431,7 @@ export default class TestGenerator {
|
|
|
431
431
|
` expect(res.status).toBe(401)`,
|
|
432
432
|
` })`,
|
|
433
433
|
'})',
|
|
434
|
-
''
|
|
434
|
+
''
|
|
435
435
|
)
|
|
436
436
|
|
|
437
437
|
return {
|
|
@@ -525,7 +525,7 @@ export default class TestGenerator {
|
|
|
525
525
|
` const res = await request('GET', \`${routePath}/\${createdId}\`)`,
|
|
526
526
|
` expect(res.status).toBe(200)`,
|
|
527
527
|
` })`,
|
|
528
|
-
''
|
|
528
|
+
''
|
|
529
529
|
)
|
|
530
530
|
|
|
531
531
|
if (Object.keys(updatePayload).length > 0) {
|
|
@@ -534,7 +534,7 @@ export default class TestGenerator {
|
|
|
534
534
|
` const res = await request('PUT', \`${routePath}/\${createdId}\`, ${updatePayload})`,
|
|
535
535
|
` expect(res.status).toBe(200)`,
|
|
536
536
|
` })`,
|
|
537
|
-
''
|
|
537
|
+
''
|
|
538
538
|
)
|
|
539
539
|
}
|
|
540
540
|
|
|
@@ -550,7 +550,7 @@ export default class TestGenerator {
|
|
|
550
550
|
` const res = await request('GET', \`${routePath}/\${createdId}\`)`,
|
|
551
551
|
` expect(res.status).toBe(404)`,
|
|
552
552
|
` })`,
|
|
553
|
-
''
|
|
553
|
+
''
|
|
554
554
|
)
|
|
555
555
|
|
|
556
556
|
// Validation test
|
|
@@ -562,7 +562,7 @@ export default class TestGenerator {
|
|
|
562
562
|
` const body = (await res.json()) as any`,
|
|
563
563
|
` expect(body.errors).toBeDefined()`,
|
|
564
564
|
` })`,
|
|
565
|
-
''
|
|
565
|
+
''
|
|
566
566
|
)
|
|
567
567
|
}
|
|
568
568
|
|
|
@@ -572,7 +572,7 @@ export default class TestGenerator {
|
|
|
572
572
|
` expect(res.status).toBe(404)`,
|
|
573
573
|
` })`,
|
|
574
574
|
'})',
|
|
575
|
-
''
|
|
575
|
+
''
|
|
576
576
|
)
|
|
577
577
|
|
|
578
578
|
return {
|
|
@@ -585,7 +585,10 @@ export default class TestGenerator {
|
|
|
585
585
|
// Contribution tests (dependent, full CRUD + soft delete)
|
|
586
586
|
// ---------------------------------------------------------------------------
|
|
587
587
|
|
|
588
|
-
private generateContributionTest(
|
|
588
|
+
private generateContributionTest(
|
|
589
|
+
schema: SchemaDefinition,
|
|
590
|
+
table: TableDefinition
|
|
591
|
+
): GeneratedFile {
|
|
589
592
|
const snakeName = toSnakeCase(schema.name)
|
|
590
593
|
const displayName = snakeName.replace(/_/g, ' ')
|
|
591
594
|
const parentName = schema.parent!
|
|
@@ -609,18 +612,16 @@ export default class TestGenerator {
|
|
|
609
612
|
}
|
|
610
613
|
if (parentChain.some(p => !p.isTestUser)) lines.push('')
|
|
611
614
|
|
|
612
|
-
lines.push(
|
|
613
|
-
'beforeAll(async () => {',
|
|
614
|
-
' await cleanup()',
|
|
615
|
-
' await createTestUser()',
|
|
616
|
-
)
|
|
615
|
+
lines.push('beforeAll(async () => {', ' await cleanup()', ' await createTestUser()')
|
|
617
616
|
|
|
618
617
|
// Create parent chain
|
|
619
618
|
for (const p of parentChain) {
|
|
620
619
|
if (p.isTestUser) continue
|
|
621
620
|
lines.push('')
|
|
622
621
|
lines.push(` // Create ${p.schemaName} to serve as parent`)
|
|
623
|
-
lines.push(
|
|
622
|
+
lines.push(
|
|
623
|
+
` const ${p.varName}Res = await request('POST', \`${p.createPath}\`, ${p.createPayload})`
|
|
624
|
+
)
|
|
624
625
|
lines.push(` const ${p.varName}Data = (await ${p.varName}Res.json()) as any`)
|
|
625
626
|
lines.push(` ${p.varName} = ${p.varName}Data.${p.pkField}`)
|
|
626
627
|
}
|
|
@@ -634,7 +635,7 @@ export default class TestGenerator {
|
|
|
634
635
|
'',
|
|
635
636
|
`describe('contribution archetype: ${snakeName} (full CRUD${hasSoftDelete ? ' + soft delete' : ''}, parent: ${parentName})', () => {`,
|
|
636
637
|
` let createdId: string | number`,
|
|
637
|
-
''
|
|
638
|
+
''
|
|
638
639
|
)
|
|
639
640
|
|
|
640
641
|
const testRoutePath = this.buildTestRoutePath(schema, parentChain)
|
|
@@ -643,7 +644,7 @@ export default class TestGenerator {
|
|
|
643
644
|
` test('POST ${routePath} → 201 (create with parent FK)', async () => {`,
|
|
644
645
|
` const res = await request('POST', \`${testRoutePath}\`, ${storePayload})`,
|
|
645
646
|
` expect(res.status).toBe(201)`,
|
|
646
|
-
` const data = (await res.json()) as any
|
|
647
|
+
` const data = (await res.json()) as any`
|
|
647
648
|
)
|
|
648
649
|
const firstField = this.getFirstFieldForAssertion(schema)
|
|
649
650
|
if (firstField) {
|
|
@@ -666,7 +667,7 @@ export default class TestGenerator {
|
|
|
666
667
|
` const res = await request('GET', \`${testRoutePath}/\${createdId}\`)`,
|
|
667
668
|
` expect(res.status).toBe(200)`,
|
|
668
669
|
` })`,
|
|
669
|
-
''
|
|
670
|
+
''
|
|
670
671
|
)
|
|
671
672
|
|
|
672
673
|
if (Object.keys(updatePayload).length > 0) {
|
|
@@ -675,7 +676,7 @@ export default class TestGenerator {
|
|
|
675
676
|
` const res = await request('PUT', \`${testRoutePath}/\${createdId}\`, ${updatePayload})`,
|
|
676
677
|
` expect(res.status).toBe(200)`,
|
|
677
678
|
` })`,
|
|
678
|
-
''
|
|
679
|
+
''
|
|
679
680
|
)
|
|
680
681
|
}
|
|
681
682
|
|
|
@@ -692,7 +693,7 @@ export default class TestGenerator {
|
|
|
692
693
|
` expect(res.status).toBe(404)`,
|
|
693
694
|
` })`,
|
|
694
695
|
'})',
|
|
695
|
-
''
|
|
696
|
+
''
|
|
696
697
|
)
|
|
697
698
|
|
|
698
699
|
return {
|
|
@@ -756,7 +757,7 @@ export default class TestGenerator {
|
|
|
756
757
|
` const res = await request('GET', \`${routePath}/\${createdId}\`)`,
|
|
757
758
|
` expect(res.status).toBe(200)`,
|
|
758
759
|
` })`,
|
|
759
|
-
''
|
|
760
|
+
''
|
|
760
761
|
)
|
|
761
762
|
|
|
762
763
|
if (Object.keys(updatePayload).length > 0) {
|
|
@@ -765,7 +766,7 @@ export default class TestGenerator {
|
|
|
765
766
|
` const res = await request('PUT', \`${routePath}/\${createdId}\`, ${updatePayload})`,
|
|
766
767
|
` expect(res.status).toBe(200)`,
|
|
767
768
|
` })`,
|
|
768
|
-
''
|
|
769
|
+
''
|
|
769
770
|
)
|
|
770
771
|
}
|
|
771
772
|
|
|
@@ -782,7 +783,7 @@ export default class TestGenerator {
|
|
|
782
783
|
` expect(res.status).toBe(404)`,
|
|
783
784
|
` })`,
|
|
784
785
|
'})',
|
|
785
|
-
''
|
|
786
|
+
''
|
|
786
787
|
)
|
|
787
788
|
|
|
788
789
|
return {
|
|
@@ -816,17 +817,15 @@ export default class TestGenerator {
|
|
|
816
817
|
}
|
|
817
818
|
if (parentChain.some(p => !p.isTestUser)) lines.push('')
|
|
818
819
|
|
|
819
|
-
lines.push(
|
|
820
|
-
'beforeAll(async () => {',
|
|
821
|
-
' await cleanup()',
|
|
822
|
-
' await createTestUser()',
|
|
823
|
-
)
|
|
820
|
+
lines.push('beforeAll(async () => {', ' await cleanup()', ' await createTestUser()')
|
|
824
821
|
|
|
825
822
|
for (const p of parentChain) {
|
|
826
823
|
if (p.isTestUser) continue
|
|
827
824
|
lines.push('')
|
|
828
825
|
lines.push(` // Create a ${p.schemaName} to serve as parent`)
|
|
829
|
-
lines.push(
|
|
826
|
+
lines.push(
|
|
827
|
+
` const ${p.varName}Res = await request('POST', \`${p.createPath}\`, ${p.createPayload})`
|
|
828
|
+
)
|
|
830
829
|
lines.push(` const ${p.varName}Data = (await ${p.varName}Res.json()) as any`)
|
|
831
830
|
lines.push(` ${p.varName} = ${p.varName}Data.${p.pkField}`)
|
|
832
831
|
}
|
|
@@ -840,7 +839,7 @@ export default class TestGenerator {
|
|
|
840
839
|
'',
|
|
841
840
|
`describe('attribute archetype: ${snakeName} (full CRUD, parent: ${parentName})', () => {`,
|
|
842
841
|
` let createdId: string | number`,
|
|
843
|
-
''
|
|
842
|
+
''
|
|
844
843
|
)
|
|
845
844
|
|
|
846
845
|
const testRoutePath = this.buildTestRoutePath(schema, parentChain)
|
|
@@ -866,7 +865,7 @@ export default class TestGenerator {
|
|
|
866
865
|
` const res = await request('GET', \`${testRoutePath}/\${createdId}\`)`,
|
|
867
866
|
` expect(res.status).toBe(200)`,
|
|
868
867
|
` })`,
|
|
869
|
-
''
|
|
868
|
+
''
|
|
870
869
|
)
|
|
871
870
|
|
|
872
871
|
if (Object.keys(updatePayload).length > 0) {
|
|
@@ -875,7 +874,7 @@ export default class TestGenerator {
|
|
|
875
874
|
` const res = await request('PUT', \`${testRoutePath}/\${createdId}\`, ${updatePayload})`,
|
|
876
875
|
` expect(res.status).toBe(200)`,
|
|
877
876
|
` })`,
|
|
878
|
-
''
|
|
877
|
+
''
|
|
879
878
|
)
|
|
880
879
|
}
|
|
881
880
|
|
|
@@ -892,7 +891,7 @@ export default class TestGenerator {
|
|
|
892
891
|
` expect(res.status).toBe(404)`,
|
|
893
892
|
` })`,
|
|
894
893
|
'})',
|
|
895
|
-
''
|
|
894
|
+
''
|
|
896
895
|
)
|
|
897
896
|
|
|
898
897
|
return {
|
|
@@ -929,17 +928,15 @@ export default class TestGenerator {
|
|
|
929
928
|
lines.push('let contentId: string | number')
|
|
930
929
|
lines.push('')
|
|
931
930
|
|
|
932
|
-
lines.push(
|
|
933
|
-
'beforeAll(async () => {',
|
|
934
|
-
' await cleanup()',
|
|
935
|
-
' await createTestUser()',
|
|
936
|
-
)
|
|
931
|
+
lines.push('beforeAll(async () => {', ' await cleanup()', ' await createTestUser()')
|
|
937
932
|
|
|
938
933
|
for (const p of parentChain) {
|
|
939
934
|
if (p.isTestUser) continue
|
|
940
935
|
lines.push('')
|
|
941
936
|
lines.push(` // Create a ${p.schemaName} to serve as parent`)
|
|
942
|
-
lines.push(
|
|
937
|
+
lines.push(
|
|
938
|
+
` const ${p.varName}Res = await request('POST', \`${p.createPath}\`, ${p.createPayload})`
|
|
939
|
+
)
|
|
943
940
|
lines.push(` const ${p.varName}Data = (await ${p.varName}Res.json()) as any`)
|
|
944
941
|
lines.push(` ${p.varName} = ${p.varName}Data.${p.pkField}`)
|
|
945
942
|
}
|
|
@@ -963,7 +960,7 @@ export default class TestGenerator {
|
|
|
963
960
|
'})',
|
|
964
961
|
'',
|
|
965
962
|
`describe('component archetype: ${snakeName} (index, show, update only)', () => {`,
|
|
966
|
-
''
|
|
963
|
+
''
|
|
967
964
|
)
|
|
968
965
|
|
|
969
966
|
const testRoutePath = this.buildTestRoutePath(schema, parentChain)
|
|
@@ -981,7 +978,7 @@ export default class TestGenerator {
|
|
|
981
978
|
` const res = await request('GET', \`${testRoutePath}/\${contentId}\`)`,
|
|
982
979
|
` expect(res.status).toBe(200)`,
|
|
983
980
|
` })`,
|
|
984
|
-
''
|
|
981
|
+
''
|
|
985
982
|
)
|
|
986
983
|
|
|
987
984
|
if (Object.keys(updatePayload).length > 0) {
|
|
@@ -990,7 +987,7 @@ export default class TestGenerator {
|
|
|
990
987
|
` const res = await request('PUT', \`${testRoutePath}/\${contentId}\`, ${updatePayload})`,
|
|
991
988
|
` expect(res.status).toBe(200)`,
|
|
992
989
|
` })`,
|
|
993
|
-
''
|
|
990
|
+
''
|
|
994
991
|
)
|
|
995
992
|
}
|
|
996
993
|
|
|
@@ -1005,7 +1002,7 @@ export default class TestGenerator {
|
|
|
1005
1002
|
` expect(res.status).toBe(404)`,
|
|
1006
1003
|
` })`,
|
|
1007
1004
|
'})',
|
|
1008
|
-
''
|
|
1005
|
+
''
|
|
1009
1006
|
)
|
|
1010
1007
|
|
|
1011
1008
|
return {
|
|
@@ -1038,17 +1035,15 @@ export default class TestGenerator {
|
|
|
1038
1035
|
}
|
|
1039
1036
|
if (parentChain.some(p => !p.isTestUser)) lines.push('')
|
|
1040
1037
|
|
|
1041
|
-
lines.push(
|
|
1042
|
-
'beforeAll(async () => {',
|
|
1043
|
-
' await cleanup()',
|
|
1044
|
-
' await createTestUser()',
|
|
1045
|
-
)
|
|
1038
|
+
lines.push('beforeAll(async () => {', ' await cleanup()', ' await createTestUser()')
|
|
1046
1039
|
|
|
1047
1040
|
for (const p of parentChain) {
|
|
1048
1041
|
if (p.isTestUser) continue
|
|
1049
1042
|
lines.push('')
|
|
1050
1043
|
lines.push(` // Create a ${p.schemaName} to serve as parent`)
|
|
1051
|
-
lines.push(
|
|
1044
|
+
lines.push(
|
|
1045
|
+
` const ${p.varName}Res = await request('POST', \`${p.createPath}\`, ${p.createPayload})`
|
|
1046
|
+
)
|
|
1052
1047
|
lines.push(` const ${p.varName}Data = (await ${p.varName}Res.json()) as any`)
|
|
1053
1048
|
lines.push(` ${p.varName} = ${p.varName}Data.${p.pkField}`)
|
|
1054
1049
|
}
|
|
@@ -1062,7 +1057,7 @@ export default class TestGenerator {
|
|
|
1062
1057
|
'',
|
|
1063
1058
|
`describe('event archetype: ${snakeName} (index, show, store — append only)', () => {`,
|
|
1064
1059
|
` let createdId: string | number`,
|
|
1065
|
-
''
|
|
1060
|
+
''
|
|
1066
1061
|
)
|
|
1067
1062
|
|
|
1068
1063
|
const testRoutePath = this.buildTestRoutePath(schema, parentChain)
|
|
@@ -1099,7 +1094,7 @@ export default class TestGenerator {
|
|
|
1099
1094
|
` expect(res.status).toBe(404)`,
|
|
1100
1095
|
` })`,
|
|
1101
1096
|
'})',
|
|
1102
|
-
''
|
|
1097
|
+
''
|
|
1103
1098
|
)
|
|
1104
1099
|
|
|
1105
1100
|
return {
|
|
@@ -1112,7 +1107,10 @@ export default class TestGenerator {
|
|
|
1112
1107
|
// Configuration tests (dependent, singleton — show/upsert/reset)
|
|
1113
1108
|
// ---------------------------------------------------------------------------
|
|
1114
1109
|
|
|
1115
|
-
private generateConfigurationTest(
|
|
1110
|
+
private generateConfigurationTest(
|
|
1111
|
+
schema: SchemaDefinition,
|
|
1112
|
+
table: TableDefinition
|
|
1113
|
+
): GeneratedFile {
|
|
1116
1114
|
const snakeName = toSnakeCase(schema.name)
|
|
1117
1115
|
const parentName = schema.parent!
|
|
1118
1116
|
const routePath = this.buildRoutePath(schema)
|
|
@@ -1133,17 +1131,15 @@ export default class TestGenerator {
|
|
|
1133
1131
|
}
|
|
1134
1132
|
if (parentChain.some(p => !p.isTestUser)) lines.push('')
|
|
1135
1133
|
|
|
1136
|
-
lines.push(
|
|
1137
|
-
'beforeAll(async () => {',
|
|
1138
|
-
' await cleanup()',
|
|
1139
|
-
' await createTestUser()',
|
|
1140
|
-
)
|
|
1134
|
+
lines.push('beforeAll(async () => {', ' await cleanup()', ' await createTestUser()')
|
|
1141
1135
|
|
|
1142
1136
|
for (const p of parentChain) {
|
|
1143
1137
|
if (p.isTestUser) continue
|
|
1144
1138
|
lines.push('')
|
|
1145
1139
|
lines.push(` // Create a ${p.schemaName} to serve as parent`)
|
|
1146
|
-
lines.push(
|
|
1140
|
+
lines.push(
|
|
1141
|
+
` const ${p.varName}Res = await request('POST', \`${p.createPath}\`, ${p.createPayload})`
|
|
1142
|
+
)
|
|
1147
1143
|
lines.push(` const ${p.varName}Data = (await ${p.varName}Res.json()) as any`)
|
|
1148
1144
|
lines.push(` ${p.varName} = ${p.varName}Data.${p.pkField}`)
|
|
1149
1145
|
}
|
|
@@ -1156,7 +1152,7 @@ export default class TestGenerator {
|
|
|
1156
1152
|
'})',
|
|
1157
1153
|
'',
|
|
1158
1154
|
`describe('configuration archetype: ${snakeName} (show, upsert, reset — singleton)', () => {`,
|
|
1159
|
-
''
|
|
1155
|
+
''
|
|
1160
1156
|
)
|
|
1161
1157
|
|
|
1162
1158
|
const testRoutePath = this.buildTestRoutePath(schema, parentChain)
|
|
@@ -1176,7 +1172,7 @@ export default class TestGenerator {
|
|
|
1176
1172
|
` const res = await request('GET', \`${testRoutePath}\`)`,
|
|
1177
1173
|
` expect(res.status).toBe(200)`,
|
|
1178
1174
|
` })`,
|
|
1179
|
-
''
|
|
1175
|
+
''
|
|
1180
1176
|
)
|
|
1181
1177
|
|
|
1182
1178
|
if (Object.keys(updatePayload).length > 0) {
|
|
@@ -1185,7 +1181,7 @@ export default class TestGenerator {
|
|
|
1185
1181
|
` const res = await request('PUT', \`${testRoutePath}\`, ${updatePayload})`,
|
|
1186
1182
|
` expect(res.status).toBe(200)`,
|
|
1187
1183
|
` })`,
|
|
1188
|
-
''
|
|
1184
|
+
''
|
|
1189
1185
|
)
|
|
1190
1186
|
}
|
|
1191
1187
|
|
|
@@ -1202,7 +1198,7 @@ export default class TestGenerator {
|
|
|
1202
1198
|
` expect(res.status).toBe(404)`,
|
|
1203
1199
|
` })`,
|
|
1204
1200
|
'})',
|
|
1205
|
-
''
|
|
1201
|
+
''
|
|
1206
1202
|
)
|
|
1207
1203
|
|
|
1208
1204
|
return {
|
|
@@ -1231,10 +1227,7 @@ export default class TestGenerator {
|
|
|
1231
1227
|
}
|
|
1232
1228
|
|
|
1233
1229
|
/** Build the route path with template literal variables for actual test requests. */
|
|
1234
|
-
private buildTestRoutePath(
|
|
1235
|
-
schema: SchemaDefinition,
|
|
1236
|
-
parentChain: ParentChainEntry[]
|
|
1237
|
-
): string {
|
|
1230
|
+
private buildTestRoutePath(schema: SchemaDefinition, parentChain: ParentChainEntry[]): string {
|
|
1238
1231
|
const isDependent = DEPENDENT_ARCHETYPES.has(schema.archetype)
|
|
1239
1232
|
const prefix = this.apiConfig.routing === ApiRouting.Prefix ? this.apiConfig.prefix : ''
|
|
1240
1233
|
|
|
@@ -1262,9 +1255,7 @@ export default class TestGenerator {
|
|
|
1262
1255
|
|
|
1263
1256
|
/** Find the user entity schema (used for test user creation). */
|
|
1264
1257
|
private findUserEntity(): SchemaDefinition | undefined {
|
|
1265
|
-
return this.schemas.find(
|
|
1266
|
-
s => s.archetype === Archetype.Entity && s.name === 'user'
|
|
1267
|
-
)
|
|
1258
|
+
return this.schemas.find(s => s.archetype === Archetype.Entity && s.name === 'user')
|
|
1268
1259
|
}
|
|
1269
1260
|
|
|
1270
1261
|
/** Find the primary key field name for a schema. */
|
|
@@ -1298,9 +1289,7 @@ export default class TestGenerator {
|
|
|
1298
1289
|
}
|
|
1299
1290
|
|
|
1300
1291
|
/** Find the first required non-PK field. */
|
|
1301
|
-
private findFirstRequiredField(
|
|
1302
|
-
schema: SchemaDefinition
|
|
1303
|
-
): { name: string; camel: string } | null {
|
|
1292
|
+
private findFirstRequiredField(schema: SchemaDefinition): { name: string; camel: string } | null {
|
|
1304
1293
|
for (const [fieldName, fieldDef] of Object.entries(schema.fields)) {
|
|
1305
1294
|
if (fieldDef.primaryKey) continue
|
|
1306
1295
|
if (SYSTEM_COLUMNS.has(toSnakeCase(fieldName))) continue
|
|
@@ -1346,7 +1335,9 @@ export default class TestGenerator {
|
|
|
1346
1335
|
if (fieldDef.sensitive) {
|
|
1347
1336
|
return 'hashed_fixture'
|
|
1348
1337
|
}
|
|
1349
|
-
const label = toPascalCase(fieldName)
|
|
1338
|
+
const label = toPascalCase(fieldName)
|
|
1339
|
+
.replace(/([A-Z])/g, ' $1')
|
|
1340
|
+
.trim()
|
|
1350
1341
|
return `Fixture ${label}`
|
|
1351
1342
|
}
|
|
1352
1343
|
|
|
@@ -1420,17 +1411,16 @@ export default class TestGenerator {
|
|
|
1420
1411
|
case 'character':
|
|
1421
1412
|
case 'text':
|
|
1422
1413
|
default: {
|
|
1423
|
-
const label = toPascalCase(fieldName)
|
|
1414
|
+
const label = toPascalCase(fieldName)
|
|
1415
|
+
.replace(/([A-Z])/g, ' $1')
|
|
1416
|
+
.trim()
|
|
1424
1417
|
return mode === 'js' ? `'Test ${label}'` : `Test ${label}`
|
|
1425
1418
|
}
|
|
1426
1419
|
}
|
|
1427
1420
|
}
|
|
1428
1421
|
|
|
1429
1422
|
/** Generate an updated sample value (different from store). */
|
|
1430
|
-
private sampleUpdateValue(
|
|
1431
|
-
fieldName: string,
|
|
1432
|
-
fieldDef: FieldDefinition
|
|
1433
|
-
): string {
|
|
1423
|
+
private sampleUpdateValue(fieldName: string, fieldDef: FieldDefinition): string {
|
|
1434
1424
|
if (this.isEmailField(fieldName, fieldDef)) {
|
|
1435
1425
|
return `'updated-${toSnakeCase(fieldName)}@example.com'`
|
|
1436
1426
|
}
|
|
@@ -1464,7 +1454,9 @@ export default class TestGenerator {
|
|
|
1464
1454
|
case 'money':
|
|
1465
1455
|
return '19.99'
|
|
1466
1456
|
default: {
|
|
1467
|
-
const label = toPascalCase(fieldName)
|
|
1457
|
+
const label = toPascalCase(fieldName)
|
|
1458
|
+
.replace(/([A-Z])/g, ' $1')
|
|
1459
|
+
.trim()
|
|
1468
1460
|
return `'Updated ${label}'`
|
|
1469
1461
|
}
|
|
1470
1462
|
}
|
|
@@ -1556,7 +1548,8 @@ export default class TestGenerator {
|
|
|
1556
1548
|
const parentSchema = this.schemaMap.get(current)
|
|
1557
1549
|
if (!parentSchema) break
|
|
1558
1550
|
|
|
1559
|
-
const isUserEntity =
|
|
1551
|
+
const isUserEntity =
|
|
1552
|
+
parentSchema.archetype === Archetype.Entity && parentSchema.name === 'user'
|
|
1560
1553
|
const pkName = this.findSchemaPK(parentSchema.name)
|
|
1561
1554
|
|
|
1562
1555
|
chain.unshift({
|
|
@@ -1582,7 +1575,9 @@ export default class TestGenerator {
|
|
|
1582
1575
|
// Build the path to create this parent
|
|
1583
1576
|
if (DEPENDENT_ARCHETYPES.has(parentSchema.archetype) && parentSchema.parent) {
|
|
1584
1577
|
const grandParent = chain[i - 1]
|
|
1585
|
-
const grandParentVar = grandParent?.isTestUser
|
|
1578
|
+
const grandParentVar = grandParent?.isTestUser
|
|
1579
|
+
? 'testUserPid'
|
|
1580
|
+
: (grandParent?.varName ?? 'testUserPid')
|
|
1586
1581
|
const parentSegment = toRouteSegment(parentSchema.parent)
|
|
1587
1582
|
const childSegment = toChildSegment(parentSchema.name, parentSchema.parent)
|
|
1588
1583
|
const prefix = this.apiConfig.routing === ApiRouting.Prefix ? this.apiConfig.prefix : ''
|
|
@@ -1631,12 +1626,7 @@ export default class TestGenerator {
|
|
|
1631
1626
|
}
|
|
1632
1627
|
dependents.sort((a, b) => (depthMap.get(b) ?? 0) - (depthMap.get(a) ?? 0))
|
|
1633
1628
|
|
|
1634
|
-
return [
|
|
1635
|
-
...associations,
|
|
1636
|
-
...dependents,
|
|
1637
|
-
'_strav_access_tokens',
|
|
1638
|
-
...roots,
|
|
1639
|
-
]
|
|
1629
|
+
return [...associations, ...dependents, '_strav_access_tokens', ...roots]
|
|
1640
1630
|
}
|
|
1641
1631
|
|
|
1642
1632
|
/** Compute the nesting depth of a schema (0 for root, 1 for direct child, etc.). */
|
package/src/i18n/helpers.ts
CHANGED
|
@@ -65,7 +65,11 @@ export function t(key: string, replacements?: Record<string, string | number>):
|
|
|
65
65
|
* choice('messages.apple', 1) // "one apple"
|
|
66
66
|
* choice('messages.apple', 5) // "5 apples"
|
|
67
67
|
*/
|
|
68
|
-
export function choice(
|
|
68
|
+
export function choice(
|
|
69
|
+
key: string,
|
|
70
|
+
count: number,
|
|
71
|
+
replacements?: Record<string, string | number>
|
|
72
|
+
): string {
|
|
69
73
|
if (!I18nManager.isLoaded) return key
|
|
70
74
|
|
|
71
75
|
return translateChoice(locale(), key, count, replacements)
|
package/src/i18n/i18n_manager.ts
CHANGED
|
@@ -38,7 +38,9 @@ export default class I18nManager {
|
|
|
38
38
|
|
|
39
39
|
static get config(): I18nConfig {
|
|
40
40
|
if (!I18nManager._config) {
|
|
41
|
-
throw new ConfigurationError(
|
|
41
|
+
throw new ConfigurationError(
|
|
42
|
+
'I18nManager not configured. Resolve it through the container first.'
|
|
43
|
+
)
|
|
42
44
|
}
|
|
43
45
|
return I18nManager._config
|
|
44
46
|
}
|
package/src/i18n/middleware.ts
CHANGED
|
@@ -42,10 +42,7 @@ function detectLocale(ctx: Context): string {
|
|
|
42
42
|
break
|
|
43
43
|
}
|
|
44
44
|
case 'header': {
|
|
45
|
-
const match = parseAcceptLanguage(
|
|
46
|
-
ctx.headers.get('accept-language'),
|
|
47
|
-
supported
|
|
48
|
-
)
|
|
45
|
+
const match = parseAcceptLanguage(ctx.headers.get('accept-language'), supported)
|
|
49
46
|
if (match) return match
|
|
50
47
|
break
|
|
51
48
|
}
|
|
@@ -62,10 +59,7 @@ function detectLocale(ctx: Context): string {
|
|
|
62
59
|
* @example
|
|
63
60
|
* parseAcceptLanguage('fr-FR,fr;q=0.9,en;q=0.8', ['en', 'fr']) // 'fr'
|
|
64
61
|
*/
|
|
65
|
-
export function parseAcceptLanguage(
|
|
66
|
-
header: string | null,
|
|
67
|
-
supported: string[]
|
|
68
|
-
): string | null {
|
|
62
|
+
export function parseAcceptLanguage(header: string | null, supported: string[]): string | null {
|
|
69
63
|
if (!header) return null
|
|
70
64
|
|
|
71
65
|
// Parse entries like "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7"
|
package/src/mail/helpers.ts
CHANGED
|
@@ -205,7 +205,7 @@ export const mail = {
|
|
|
205
205
|
* Call this in your app bootstrap after Queue is configured.
|
|
206
206
|
*/
|
|
207
207
|
registerQueueHandler(): void {
|
|
208
|
-
Queue.handle<MailMessage>('strav:send-mail', async
|
|
208
|
+
Queue.handle<MailMessage>('strav:send-mail', async message => {
|
|
209
209
|
await MailManager.transport.send(message)
|
|
210
210
|
})
|
|
211
211
|
},
|
package/src/mail/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { mail, PendingMail } from './helpers.ts'
|
|
|
3
3
|
export { SmtpTransport } from './transports/smtp_transport.ts'
|
|
4
4
|
export { ResendTransport } from './transports/resend_transport.ts'
|
|
5
5
|
export { SendGridTransport } from './transports/sendgrid_transport.ts'
|
|
6
|
+
export { MailgunTransport } from './transports/mailgun_transport.ts'
|
|
7
|
+
export { AlibabaTransport } from './transports/alibaba_transport.ts'
|
|
6
8
|
export { LogTransport } from './transports/log_transport.ts'
|
|
7
9
|
export { inlineCss } from './css_inliner.ts'
|
|
8
10
|
export type {
|
|
@@ -14,6 +16,8 @@ export type {
|
|
|
14
16
|
SmtpConfig,
|
|
15
17
|
ResendConfig,
|
|
16
18
|
SendGridConfig,
|
|
19
|
+
MailgunConfig,
|
|
20
|
+
AlibabaConfig,
|
|
17
21
|
LogConfig,
|
|
18
22
|
} from './types.ts'
|
|
19
23
|
export type { InlinerOptions } from './css_inliner.ts'
|
package/src/mail/mail_manager.ts
CHANGED
|
@@ -4,6 +4,8 @@ import Configuration from '../config/configuration.ts'
|
|
|
4
4
|
import { SmtpTransport } from './transports/smtp_transport.ts'
|
|
5
5
|
import { ResendTransport } from './transports/resend_transport.ts'
|
|
6
6
|
import { SendGridTransport } from './transports/sendgrid_transport.ts'
|
|
7
|
+
import { MailgunTransport } from './transports/mailgun_transport.ts'
|
|
8
|
+
import { AlibabaTransport } from './transports/alibaba_transport.ts'
|
|
7
9
|
import { LogTransport } from './transports/log_transport.ts'
|
|
8
10
|
import type { MailTransport, MailConfig } from './types.ts'
|
|
9
11
|
|
|
@@ -48,6 +50,17 @@ export default class MailManager {
|
|
|
48
50
|
apiKey: '',
|
|
49
51
|
...(config.get('mail.sendgrid', {}) as object),
|
|
50
52
|
},
|
|
53
|
+
mailgun: {
|
|
54
|
+
apiKey: '',
|
|
55
|
+
domain: '',
|
|
56
|
+
...(config.get('mail.mailgun', {}) as object),
|
|
57
|
+
},
|
|
58
|
+
alibaba: {
|
|
59
|
+
accessKeyId: '',
|
|
60
|
+
accessKeySecret: '',
|
|
61
|
+
accountName: '',
|
|
62
|
+
...(config.get('mail.alibaba', {}) as object),
|
|
63
|
+
},
|
|
51
64
|
log: {
|
|
52
65
|
output: 'console',
|
|
53
66
|
...(config.get('mail.log', {}) as object),
|
|
@@ -65,6 +78,10 @@ export default class MailManager {
|
|
|
65
78
|
return new ResendTransport(MailManager._config.resend)
|
|
66
79
|
case 'sendgrid':
|
|
67
80
|
return new SendGridTransport(MailManager._config.sendgrid)
|
|
81
|
+
case 'mailgun':
|
|
82
|
+
return new MailgunTransport(MailManager._config.mailgun)
|
|
83
|
+
case 'alibaba':
|
|
84
|
+
return new AlibabaTransport(MailManager._config.alibaba)
|
|
68
85
|
case 'log':
|
|
69
86
|
return new LogTransport(MailManager._config.log)
|
|
70
87
|
default:
|
|
@@ -76,7 +93,9 @@ export default class MailManager {
|
|
|
76
93
|
|
|
77
94
|
static get transport(): MailTransport {
|
|
78
95
|
if (!MailManager._transport) {
|
|
79
|
-
throw new ConfigurationError(
|
|
96
|
+
throw new ConfigurationError(
|
|
97
|
+
'MailManager not configured. Resolve it through the container first.'
|
|
98
|
+
)
|
|
80
99
|
}
|
|
81
100
|
return MailManager._transport
|
|
82
101
|
}
|