1+ package integration
2+
3+ import (
4+ "log/slog"
5+ "os"
6+ "strings"
7+ "testing"
8+
9+ modular "github.com/GoCodeAlone/modular"
10+ )
11+
12+ // TestConfigProvenanceAndRequiredFieldFailureReporting tests T026: Integration config provenance & required field failure reporting
13+ // This test verifies that configuration errors include proper provenance information
14+ // and that required field failures are clearly reported with context.
15+ func TestConfigProvenanceAndRequiredFieldFailureReporting (t * testing.T ) {
16+ logger := slog .New (slog .NewTextHandler (os .Stdout , & slog.HandlerOptions {Level : slog .LevelDebug }))
17+
18+ // Test case 1: Required field missing
19+ t .Run ("RequiredFieldMissing" , func (t * testing.T ) {
20+ // Create a config module that requires certain fields
21+ configModule := & testConfigModule {
22+ name : "configTestModule" ,
23+ config : & testModuleConfig {
24+ // Leave RequiredField empty to trigger validation error
25+ RequiredField : "" ,
26+ OptionalField : "present" ,
27+ },
28+ }
29+
30+ // Create application
31+ app := modular .NewStdApplication (modular .NewStdConfigProvider (& struct {}{}), logger )
32+ app .RegisterModule (configModule )
33+
34+ // Initialize application - should fail due to missing required field
35+ err := app .Init ()
36+ if err == nil {
37+ t .Fatal ("Expected initialization to fail due to missing required field, but it succeeded" )
38+ }
39+
40+ // Verify error contains provenance information
41+ errorStr := err .Error ()
42+ t .Logf ("Configuration error: %s" , errorStr )
43+
44+ // Check for expected error elements:
45+ // 1. Module name should be mentioned
46+ if ! strings .Contains (errorStr , "configTestModule" ) {
47+ t .Errorf ("Error should contain module name 'configTestModule', got: %s" , errorStr )
48+ }
49+
50+ // 2. Field name should be mentioned
51+ if ! strings .Contains (errorStr , "RequiredField" ) {
52+ t .Errorf ("Error should contain field name 'RequiredField', got: %s" , errorStr )
53+ }
54+
55+ // 3. Should indicate it's a validation/required field issue
56+ if ! (strings .Contains (errorStr , "required" ) || strings .Contains (errorStr , "validation" ) || strings .Contains (errorStr , "missing" )) {
57+ t .Errorf ("Error should indicate required/validation issue, got: %s" , errorStr )
58+ }
59+
60+ t .Log ("✅ Required field error properly reported with context" )
61+ })
62+
63+ // Test case 2: Invalid field value
64+ t .Run ("InvalidFieldValue" , func (t * testing.T ) {
65+ // Create a config module with invalid field value
66+ configModule := & testConfigModule {
67+ name : "configTestModule2" ,
68+ config : & testModuleConfig {
69+ RequiredField : "present" ,
70+ OptionalField : "present" ,
71+ NumericField : - 1 , // Invalid value (should be positive)
72+ },
73+ }
74+
75+ // Create application
76+ app := modular .NewStdApplication (modular .NewStdConfigProvider (& struct {}{}), logger )
77+ app .RegisterModule (configModule )
78+
79+ // Initialize application - should fail due to invalid field value
80+ err := app .Init ()
81+ if err == nil {
82+ t .Fatal ("Expected initialization to fail due to invalid field value, but it succeeded" )
83+ }
84+
85+ errorStr := err .Error ()
86+ t .Logf ("Validation error: %s" , errorStr )
87+
88+ // Verify error contains context about the invalid value
89+ if ! strings .Contains (errorStr , "configTestModule2" ) {
90+ t .Errorf ("Error should contain module name 'configTestModule2', got: %s" , errorStr )
91+ }
92+
93+ t .Log ("✅ Invalid field value error properly reported" )
94+ })
95+
96+ // Test case 3: Configuration source tracking (provenance)
97+ t .Run ("ConfigurationProvenance" , func (t * testing.T ) {
98+ // This test verifies that configuration errors include information about
99+ // where the configuration came from (file, env var, default, etc.)
100+
101+ // Create a module with valid config to test provenance tracking
102+ configModule := & testConfigModule {
103+ name : "provenanceTestModule" ,
104+ config : & testModuleConfig {
105+ RequiredField : "valid" ,
106+ OptionalField : "from-test" ,
107+ NumericField : 42 ,
108+ },
109+ }
110+
111+ // Create application
112+ app := modular .NewStdApplication (modular .NewStdConfigProvider (& struct {}{}), logger )
113+ app .RegisterModule (configModule )
114+
115+ // Initialize application - should succeed
116+ err := app .Init ()
117+ if err != nil {
118+ t .Fatalf ("Application initialization failed: %v" , err )
119+ }
120+
121+ // For now, just verify successful config loading
122+ // Future enhancement: track where each config value came from
123+ t .Log ("✅ Configuration loaded successfully" )
124+ t .Log ("⚠️ Note: Enhanced provenance tracking (source file/env/default) is not yet implemented" )
125+ })
126+ }
127+
128+ // TestConfigurationErrorAccumulation verifies how the framework handles multiple config errors
129+ func TestConfigurationErrorAccumulation (t * testing.T ) {
130+ logger := slog .New (slog .NewTextHandler (os .Stdout , & slog.HandlerOptions {Level : slog .LevelDebug }))
131+
132+ // Create multiple modules with different config errors
133+ module1 := & testConfigModule {
134+ name : "errorModule1" ,
135+ config : & testModuleConfig {
136+ RequiredField : "" , // Missing required field
137+ },
138+ }
139+
140+ module2 := & testConfigModule {
141+ name : "errorModule2" ,
142+ config : & testModuleConfig {
143+ RequiredField : "present" ,
144+ NumericField : - 5 , // Invalid value
145+ },
146+ }
147+
148+ module3 := & testConfigModule {
149+ name : "validModule" ,
150+ config : & testModuleConfig {
151+ RequiredField : "present" ,
152+ OptionalField : "valid" ,
153+ NumericField : 10 ,
154+ },
155+ }
156+
157+ // Create application
158+ app := modular .NewStdApplication (modular .NewStdConfigProvider (& struct {}{}), logger )
159+ app .RegisterModule (module1 )
160+ app .RegisterModule (module2 )
161+ app .RegisterModule (module3 )
162+
163+ // Initialize application - should fail at first config error
164+ err := app .Init ()
165+ if err == nil {
166+ t .Fatal ("Expected initialization to fail due to config errors, but it succeeded" )
167+ }
168+
169+ errorStr := err .Error ()
170+ t .Logf ("Configuration error (current behavior): %s" , errorStr )
171+
172+ // Current behavior: framework stops at first configuration error
173+ // Verify first error module is mentioned
174+ if ! strings .Contains (errorStr , "errorModule1" ) {
175+ t .Errorf ("Error should contain 'errorModule1', got: %s" , errorStr )
176+ }
177+
178+ // Check if this is current behavior (stops at first error) or improved behavior (collects all)
179+ if strings .Contains (errorStr , "errorModule2" ) {
180+ t .Log ("✅ Enhanced behavior: Multiple configuration errors accumulated and reported" )
181+ } else {
182+ t .Log ("⚠️ Current behavior: Framework stops at first configuration error" )
183+ t .Log ("⚠️ Note: Error accumulation for config validation not yet implemented" )
184+ }
185+
186+ t .Log ("✅ Configuration error handling behavior documented" )
187+ }
188+
189+ // testModuleConfig represents a module configuration with validation
190+ type testModuleConfig struct {
191+ RequiredField string `yaml:"required_field" json:"required_field" required:"true" desc:"This field is required"`
192+ OptionalField string `yaml:"optional_field" json:"optional_field" default:"default_value" desc:"This field is optional"`
193+ NumericField int `yaml:"numeric_field" json:"numeric_field" default:"1" desc:"Must be positive"`
194+ }
195+
196+ // Validate implements the ConfigValidator interface
197+ func (cfg * testModuleConfig ) Validate () error {
198+ if cfg .RequiredField == "" {
199+ return modular .ErrConfigValidationFailed
200+ }
201+ if cfg .NumericField < 0 {
202+ return modular .ErrConfigValidationFailed
203+ }
204+ return nil
205+ }
206+
207+ // testConfigModule is a module that uses configuration with validation
208+ type testConfigModule struct {
209+ name string
210+ config * testModuleConfig
211+ }
212+
213+ func (m * testConfigModule ) Name () string {
214+ return m .name
215+ }
216+
217+ func (m * testConfigModule ) RegisterConfig (app modular.Application ) error {
218+ // Register the configuration section
219+ provider := modular .NewStdConfigProvider (m .config )
220+ app .RegisterConfigSection (m .name , provider )
221+ return nil
222+ }
223+
224+ func (m * testConfigModule ) Init (app modular.Application ) error {
225+ // Configuration validation should have already occurred during RegisterConfig
226+ return nil
227+ }
0 commit comments