1- /// <reference types="node" />
2- import { strict as assert } from 'assert' ;
3- import { envSchema } from './config.schema' ;
1+ // src/config.test.ts
2+ // Tests for configuration source precedence and validation behavior.
3+
44import { z } from 'zod' ;
5+ import { envSchema } from './config.schema' ;
56
67/**
7- * Minimal valid env fixture — satisfies every required field in envSchema
8- * so individual tests can override only the values they care about .
8+ * Test configuration schema behavior without affecting actual config.
9+ * These tests validate the documented source precedence rules and Stellar-specific logic .
910 */
11+
12+ const booleanCoerce = z . preprocess ( ( val ) => {
13+ if ( typeof val === 'string' ) {
14+ const lower = val . toLowerCase ( ) ;
15+ if ( lower === 'true' || lower === '1' ) return true ;
16+ if ( lower === 'false' || lower === '0' ) return false ;
17+ }
18+ return val ;
19+ } , z . coerce . boolean ( ) ) ;
20+
1021const BASE_ENV = {
1122 PORT : '3000' ,
1223 MODE : 'development' ,
@@ -21,85 +32,102 @@ const BASE_ENV = {
2132 CLOUDINARY_API_KEY : 'api-key' ,
2233 CLOUDINARY_API_SECRET : 'api-secret' ,
2334 PAYSTACK_SECRET_KEY : 'pk-secret' ,
35+ APP_SECRET : 'accesslayer_default_development_secret_key_32_bytes_long' ,
2436} ;
2537
26- function run ( ) {
27- console . log ( 'Running configuration validation tests...' ) ;
38+ describe ( 'Config Validation and Source Precedence' , ( ) => {
39+
40+ describe ( 'Stellar & General Schema Validation' , ( ) => {
41+ it ( 'Defaults are applied correctly' , ( ) => {
42+ const defaults = envSchema . parse ( BASE_ENV ) ;
43+ expect ( defaults . STELLAR_NETWORK ) . toBe ( 'testnet' ) ;
44+ expect ( defaults . STELLAR_HORIZON_URL ) . toBe ( 'https://horizon-testnet.stellar.org' ) ;
45+ expect ( defaults . API_VERSION ) . toBe ( '1.0.0' ) ;
46+ expect ( defaults . ENABLE_INDEXER_DEDUPE ) . toBe ( true ) ;
47+ } ) ;
2848
29- // ── SECTION 1: Project Schema Validation (Stellar & General) ──
49+ it ( 'Valid explicit config is accepted' , ( ) => {
50+ const valid = envSchema . safeParse ( {
51+ ...BASE_ENV ,
52+ STELLAR_NETWORK : 'mainnet' ,
53+ STELLAR_HORIZON_URL : 'https://horizon.stellar.org' ,
54+ } ) ;
55+ expect ( valid . success ) . toBe ( true ) ;
56+ } ) ;
3057
31- // 1.1 Defaults are applied correctly
32- const defaults = envSchema . parse ( BASE_ENV ) ;
33- assert . equal ( defaults . STELLAR_NETWORK , 'testnet' ) ;
34- assert . equal ( defaults . STELLAR_HORIZON_URL , 'https://horizon-testnet.stellar.org' ) ;
35- assert . equal ( defaults . API_VERSION , '1.0.0' ) ;
36- assert . equal ( defaults . ENABLE_INDEXER_DEDUPE , true ) ;
58+ it ( 'Invalid STELLAR_NETWORK value is rejected' , ( ) => {
59+ const badNetwork = envSchema . safeParse ( {
60+ ...BASE_ENV ,
61+ STELLAR_NETWORK : 'devnet' ,
62+ } ) ;
63+ expect ( badNetwork . success ) . toBe ( false ) ;
64+ } ) ;
3765
38- // 1.2 Valid explicit config is accepted
39- const valid = envSchema . safeParse ( {
40- ...BASE_ENV ,
41- STELLAR_NETWORK : 'mainnet' ,
42- STELLAR_HORIZON_URL : 'https://horizon.stellar.org' ,
66+ it ( 'production MODE with testnet STELLAR_NETWORK should fail' , ( ) => {
67+ const mismatch = envSchema . safeParse ( {
68+ ...BASE_ENV ,
69+ MODE : 'production' ,
70+ STELLAR_NETWORK : 'testnet' ,
71+ } ) ;
72+ expect ( mismatch . success ) . toBe ( false ) ;
73+ if ( ! mismatch . success ) {
74+ const issue = mismatch . error . issues . find ( ( i : z . ZodIssue ) =>
75+ i . path . includes ( 'STELLAR_NETWORK' ) && i . message . includes ( 'mainnet' )
76+ ) ;
77+ expect ( issue ) . toBeDefined ( ) ;
78+ }
79+ } ) ;
4380 } ) ;
44- assert . equal ( valid . success , true , 'Valid Stellar config should parse' ) ;
4581
46- // 1.3 Invalid STELLAR_NETWORK value is rejected
47- const badNetwork = envSchema . safeParse ( {
48- ...BASE_ENV ,
49- STELLAR_NETWORK : 'devnet' ,
50- } ) ;
51- assert . equal ( badNetwork . success , false , 'Invalid STELLAR_NETWORK should fail' ) ;
82+ describe ( 'Source Precedence & Zod Behavior' , ( ) => {
83+ it ( 'Environment variable takes precedence over default' , ( ) => {
84+ const schema = z . object ( {
85+ PORT : z . coerce . number ( ) . default ( 3000 ) ,
86+ } ) ;
5287
53- // 1.4 Cross-field: production + testnet raises an issue
54- const mismatch = envSchema . safeParse ( {
55- ...BASE_ENV ,
56- MODE : 'production' ,
57- STELLAR_NETWORK : 'testnet' ,
58- } ) ;
59- assert . equal ( mismatch . success , false , 'production MODE with testnet STELLAR_NETWORK should fail' ) ;
60- if ( ! mismatch . success ) {
61- const issue = mismatch . error . issues . find ( ( i : z . ZodIssue ) =>
62- i . path . includes ( 'STELLAR_NETWORK' ) && i . message . includes ( 'mainnet' )
63- ) ;
64- assert . ok ( issue , 'Error should warn about STELLAR_NETWORK mismatch in production' ) ;
65- }
88+ const result = schema . parse ( { PORT : '4000' } ) ;
89+ expect ( result . PORT ) . toBe ( 4000 ) ;
90+ } ) ;
6691
67- // ── SECTION 2: Source Precedence & Zod Behavior (from main branch) ──
68- console . log ( 'Running source precedence and Zod behavior checks...' ) ;
92+ it ( 'Default used when environment variable not provided' , ( ) => {
93+ const schema = z . object ( {
94+ PORT : z . coerce . number ( ) . default ( 3000 ) ,
95+ } ) ;
6996
70- // 2.1 Environment variable takes precedence over default
71- {
72- const schema = z . object ( { PORT : z . coerce . number ( ) . default ( 3000 ) } ) ;
73- const result = schema . parse ( { PORT : '4000' } ) ;
74- assert . equal ( result . PORT , 4000 ) ;
75- }
97+ const result = schema . parse ( { } ) ;
98+ expect ( result . PORT ) . toBe ( 3000 ) ;
99+ } ) ;
100+
101+ it ( 'Type coercion for numbers' , ( ) => {
102+ const schema = z . object ( {
103+ PORT : z . coerce . number ( ) ,
104+ } ) ;
76105
77- // 2.2 Type coercion for numbers and booleans
78- {
79- const schema = z . object ( {
80- PORT : z . coerce . number ( ) ,
81- ENABLED : z . coerce . boolean ( ) ,
106+ const result = schema . parse ( { PORT : '4000' } ) ;
107+ expect ( result . PORT ) . toBe ( 4000 ) ;
108+ expect ( typeof result . PORT ) . toBe ( 'number' ) ;
82109 } ) ;
83- const result = schema . parse ( { PORT : '4000' , ENABLED : 'true' } ) ;
84- assert . equal ( typeof result . PORT , 'number' ) ;
85- assert . equal ( result . ENABLED , true ) ;
86-
87- // Note: z.coerce.boolean() uses Boolean(), so any non-empty string is true.
88- // If we want 'false' or '0' to be false, we'd need a custom preprocessor.
89- assert . equal ( schema . parse ( { PORT : '3000' , ENABLED : 'false' } ) . ENABLED , true ) ;
90- }
91110
92- // 2.3 Number range validation
93- {
94- const schema = z . object ( {
95- JITTER : z . coerce . number ( ) . min ( 0 ) . max ( 1 ) . default ( 0.1 ) ,
111+ it ( 'Type coercion for booleans' , ( ) => {
112+ const schema = z . object ( {
113+ ENABLED : booleanCoerce ,
114+ } ) ;
115+
116+ expect ( schema . parse ( { ENABLED : 'true' } ) . ENABLED ) . toBe ( true ) ;
117+ expect ( schema . parse ( { ENABLED : 'false' } ) . ENABLED ) . toBe ( false ) ;
118+ expect ( schema . parse ( { ENABLED : '1' } ) . ENABLED ) . toBe ( true ) ;
119+ expect ( schema . parse ( { ENABLED : '0' } ) . ENABLED ) . toBe ( false ) ;
96120 } ) ;
97- assert . equal ( schema . parse ( { } ) . JITTER , 0.1 ) ;
98- assert . ok ( ! schema . safeParse ( { JITTER : '2' } ) . success ) ;
99- assert . ok ( ! schema . safeParse ( { JITTER : '-1' } ) . success ) ;
100- }
101121
102- console . log ( '✓ All configuration tests passed' ) ;
103- }
122+ it ( 'Number range validation' , ( ) => {
123+ const schema = z . object ( {
124+ JITTER : z . coerce . number ( ) . min ( 0 ) . max ( 1 ) . default ( 0.1 ) ,
125+ } ) ;
104126
105- run ( ) ;
127+ expect ( schema . parse ( { JITTER : '0.5' } ) . JITTER ) . toBe ( 0.5 ) ;
128+ expect ( schema . parse ( { } ) . JITTER ) . toBe ( 0.1 ) ;
129+ expect ( schema . safeParse ( { JITTER : '2' } ) . success ) . toBe ( false ) ;
130+ expect ( schema . safeParse ( { JITTER : '-1' } ) . success ) . toBe ( false ) ;
131+ } ) ;
132+ } ) ;
133+ } ) ;
0 commit comments