@@ -8,13 +8,21 @@ import {
88 DatabaseLevelIsolationStrategySchema ,
99 TenantIsolationConfigSchema ,
1010 TenantSecurityPolicySchema ,
11+ DatabaseProviderSchema ,
12+ TenantConnectionConfigSchema ,
13+ TenantUsageSchema ,
14+ QuotaEnforcementResultSchema ,
1115 type Tenant ,
1216 type TenantQuota ,
1317 type RowLevelIsolationStrategy ,
1418 type SchemaLevelIsolationStrategy ,
1519 type DatabaseLevelIsolationStrategy ,
1620 type TenantIsolationConfig ,
1721 type TenantSecurityPolicy ,
22+ type DatabaseProvider ,
23+ type TenantConnectionConfig ,
24+ type TenantUsage ,
25+ type QuotaEnforcementResult ,
1826} from './tenant.zod' ;
1927
2028describe ( 'TenantIsolationLevel' , ( ) => {
@@ -505,3 +513,186 @@ describe('TenantSecurityPolicySchema', () => {
505513 expect ( parsed . compliance ?. auditRetentionDays ) . toBe ( 365 ) ;
506514 } ) ;
507515} ) ;
516+
517+ describe ( 'DatabaseProviderSchema' , ( ) => {
518+ it ( 'should accept valid database providers' , ( ) => {
519+ const providers : DatabaseProvider [ ] = [ 'turso' , 'postgres' , 'memory' ] ;
520+ providers . forEach ( ( p ) => {
521+ expect ( ( ) => DatabaseProviderSchema . parse ( p ) ) . not . toThrow ( ) ;
522+ } ) ;
523+ } ) ;
524+
525+ it ( 'should reject invalid database provider' , ( ) => {
526+ expect ( ( ) => DatabaseProviderSchema . parse ( 'mysql' ) ) . toThrow ( ) ;
527+ expect ( ( ) => DatabaseProviderSchema . parse ( 'sqlite' ) ) . toThrow ( ) ;
528+ } ) ;
529+ } ) ;
530+
531+ describe ( 'TenantConnectionConfigSchema' , ( ) => {
532+ it ( 'should accept full connection config' , ( ) => {
533+ const config : TenantConnectionConfig = {
534+ url : 'libsql://tenant-abc-myorg.turso.io' ,
535+ authToken : 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...' ,
536+ group : 'production' ,
537+ } ;
538+ const parsed = TenantConnectionConfigSchema . parse ( config ) ;
539+ expect ( parsed . url ) . toBe ( 'libsql://tenant-abc-myorg.turso.io' ) ;
540+ expect ( parsed . authToken ) . toBeDefined ( ) ;
541+ expect ( parsed . group ) . toBe ( 'production' ) ;
542+ } ) ;
543+
544+ it ( 'should accept minimal connection config (url only)' , ( ) => {
545+ const config = { url : 'file:local.db' } ;
546+ const parsed = TenantConnectionConfigSchema . parse ( config ) ;
547+ expect ( parsed . url ) . toBe ( 'file:local.db' ) ;
548+ expect ( parsed . authToken ) . toBeUndefined ( ) ;
549+ expect ( parsed . group ) . toBeUndefined ( ) ;
550+ } ) ;
551+
552+ it ( 'should reject missing url' , ( ) => {
553+ expect ( ( ) => TenantConnectionConfigSchema . parse ( { } ) ) . toThrow ( ) ;
554+ } ) ;
555+
556+ it ( 'should reject empty url' , ( ) => {
557+ expect ( ( ) => TenantConnectionConfigSchema . parse ( { url : '' } ) ) . toThrow ( ) ;
558+ } ) ;
559+ } ) ;
560+
561+ describe ( 'TenantSchema (extended fields)' , ( ) => {
562+ it ( 'should accept tenant with Turso provisioning fields' , ( ) => {
563+ const tenant = {
564+ id : 'tenant_turso_001' ,
565+ name : 'Turso Tenant' ,
566+ isolationLevel : 'isolated_db' ,
567+ databaseProvider : 'turso' ,
568+ connectionConfig : {
569+ url : 'libsql://tenant-001-myorg.turso.io' ,
570+ authToken : 'jwt-token' ,
571+ group : 'production' ,
572+ } ,
573+ provisioningStatus : 'active' ,
574+ plan : 'pro' ,
575+ } ;
576+ const parsed = TenantSchema . parse ( tenant ) ;
577+ expect ( parsed . databaseProvider ) . toBe ( 'turso' ) ;
578+ expect ( parsed . connectionConfig ?. url ) . toContain ( 'turso.io' ) ;
579+ expect ( parsed . provisioningStatus ) . toBe ( 'active' ) ;
580+ expect ( parsed . plan ) . toBe ( 'pro' ) ;
581+ } ) ;
582+
583+ it ( 'should accept all provisioning statuses' , ( ) => {
584+ const statuses = [ 'provisioning' , 'active' , 'suspended' , 'failed' , 'destroying' ] ;
585+ statuses . forEach ( ( status ) => {
586+ const tenant = {
587+ id : 'tenant_status' ,
588+ name : 'Status Test' ,
589+ isolationLevel : 'isolated_db' ,
590+ provisioningStatus : status ,
591+ } ;
592+ expect ( ( ) => TenantSchema . parse ( tenant ) ) . not . toThrow ( ) ;
593+ } ) ;
594+ } ) ;
595+
596+ it ( 'should accept all plan values' , ( ) => {
597+ const plans = [ 'free' , 'pro' , 'enterprise' ] ;
598+ plans . forEach ( ( plan ) => {
599+ const tenant = {
600+ id : 'tenant_plan' ,
601+ name : 'Plan Test' ,
602+ isolationLevel : 'isolated_db' ,
603+ plan,
604+ } ;
605+ expect ( ( ) => TenantSchema . parse ( tenant ) ) . not . toThrow ( ) ;
606+ } ) ;
607+ } ) ;
608+
609+ it ( 'should remain backward-compatible (new fields are optional)' , ( ) => {
610+ const legacyTenant = {
611+ id : 'tenant_legacy' ,
612+ name : 'Legacy Tenant' ,
613+ isolationLevel : 'shared_schema' ,
614+ } ;
615+ const parsed = TenantSchema . parse ( legacyTenant ) ;
616+ expect ( parsed . databaseProvider ) . toBeUndefined ( ) ;
617+ expect ( parsed . connectionConfig ) . toBeUndefined ( ) ;
618+ expect ( parsed . provisioningStatus ) . toBeUndefined ( ) ;
619+ expect ( parsed . plan ) . toBeUndefined ( ) ;
620+ } ) ;
621+ } ) ;
622+
623+ describe ( 'TenantQuotaSchema (extended fields)' , ( ) => {
624+ it ( 'should accept extended quota fields' , ( ) => {
625+ const quota : TenantQuota = {
626+ maxUsers : 100 ,
627+ maxStorage : 10737418240 ,
628+ apiRateLimit : 1000 ,
629+ maxObjects : 50 ,
630+ maxRecordsPerObject : 100000 ,
631+ maxDeploymentsPerDay : 10 ,
632+ maxStorageBytes : 5368709120 ,
633+ } ;
634+ const parsed = TenantQuotaSchema . parse ( quota ) ;
635+ expect ( parsed . maxObjects ) . toBe ( 50 ) ;
636+ expect ( parsed . maxRecordsPerObject ) . toBe ( 100000 ) ;
637+ expect ( parsed . maxDeploymentsPerDay ) . toBe ( 10 ) ;
638+ expect ( parsed . maxStorageBytes ) . toBe ( 5368709120 ) ;
639+ } ) ;
640+
641+ it ( 'should accept empty quota (all optional)' , ( ) => {
642+ const parsed = TenantQuotaSchema . parse ( { } ) ;
643+ expect ( parsed . maxObjects ) . toBeUndefined ( ) ;
644+ expect ( parsed . maxRecordsPerObject ) . toBeUndefined ( ) ;
645+ } ) ;
646+ } ) ;
647+
648+ describe ( 'TenantUsageSchema' , ( ) => {
649+ it ( 'should accept full usage tracking' , ( ) => {
650+ const usage : TenantUsage = {
651+ objectCount : 15 ,
652+ totalRecords : 50000 ,
653+ storageBytes : 1073741824 ,
654+ deploymentsToday : 3 ,
655+ lastUpdatedAt : '2026-01-15T10:30:00Z' ,
656+ } ;
657+ const parsed = TenantUsageSchema . parse ( usage ) ;
658+ expect ( parsed . objectCount ) . toBe ( 15 ) ;
659+ expect ( parsed . totalRecords ) . toBe ( 50000 ) ;
660+ expect ( parsed . deploymentsToday ) . toBe ( 3 ) ;
661+ } ) ;
662+
663+ it ( 'should apply zero defaults' , ( ) => {
664+ const parsed = TenantUsageSchema . parse ( { } ) ;
665+ expect ( parsed . objectCount ) . toBe ( 0 ) ;
666+ expect ( parsed . totalRecords ) . toBe ( 0 ) ;
667+ expect ( parsed . storageBytes ) . toBe ( 0 ) ;
668+ expect ( parsed . deploymentsToday ) . toBe ( 0 ) ;
669+ } ) ;
670+
671+ it ( 'should reject negative values' , ( ) => {
672+ expect ( ( ) => TenantUsageSchema . parse ( { objectCount : - 1 } ) ) . toThrow ( ) ;
673+ } ) ;
674+ } ) ;
675+
676+ describe ( 'QuotaEnforcementResultSchema' , ( ) => {
677+ it ( 'should accept allowed result' , ( ) => {
678+ const result : QuotaEnforcementResult = {
679+ allowed : true ,
680+ } ;
681+ expect ( ( ) => QuotaEnforcementResultSchema . parse ( result ) ) . not . toThrow ( ) ;
682+ } ) ;
683+
684+ it ( 'should accept denied result with details' , ( ) => {
685+ const result : QuotaEnforcementResult = {
686+ allowed : false ,
687+ exceededQuota : 'maxObjects' ,
688+ currentUsage : 50 ,
689+ limit : 50 ,
690+ message : 'Maximum number of custom objects reached (50/50)' ,
691+ } ;
692+ const parsed = QuotaEnforcementResultSchema . parse ( result ) ;
693+ expect ( parsed . allowed ) . toBe ( false ) ;
694+ expect ( parsed . exceededQuota ) . toBe ( 'maxObjects' ) ;
695+ expect ( parsed . currentUsage ) . toBe ( 50 ) ;
696+ expect ( parsed . limit ) . toBe ( 50 ) ;
697+ } ) ;
698+ } ) ;
0 commit comments