@@ -416,6 +416,144 @@ func TestE2E_Middleware_CORS(t *testing.T) {
416416 t .Log ("E2E Middleware CORS: Allowed, disallowed, headers, and preflight scenarios verified" )
417417}
418418
419+ // TestE2E_Middleware_CORS_FullConfig verifies that the CORS middleware factory correctly
420+ // applies allowedHeaders, allowCredentials, maxAge, and wildcard subdomain origin matching.
421+ func TestE2E_Middleware_CORS_FullConfig (t * testing.T ) {
422+ port := getFreePort (t )
423+ addr := fmt .Sprintf (":%d" , port )
424+ baseURL := fmt .Sprintf ("http://127.0.0.1:%d" , port )
425+
426+ cfg := & config.WorkflowConfig {
427+ Modules : []config.ModuleConfig {
428+ {Name : "fc-server" , Type : "http.server" , Config : map [string ]any {"address" : addr }},
429+ {Name : "fc-router" , Type : "http.router" , DependsOn : []string {"fc-server" }},
430+ {Name : "fc-handler" , Type : "http.handler" , DependsOn : []string {"fc-router" }, Config : map [string ]any {"contentType" : "application/json" }},
431+ {Name : "fc-cors" , Type : "http.middleware.cors" , DependsOn : []string {"fc-router" }, Config : map [string ]any {
432+ "allowedOrigins" : []any {"*.example.com" , "https://trusted.io" },
433+ "allowedMethods" : []any {"GET" , "POST" , "OPTIONS" },
434+ "allowedHeaders" : []any {"Authorization" , "Content-Type" , "X-CSRF-Token" , "X-Request-Id" },
435+ "allowCredentials" : true ,
436+ "maxAge" : 3600 ,
437+ }},
438+ },
439+ Workflows : map [string ]any {
440+ "http" : map [string ]any {
441+ "server" : "fc-server" ,
442+ "router" : "fc-router" ,
443+ "routes" : []any {
444+ map [string ]any {
445+ "method" : "GET" ,
446+ "path" : "/api/fc-test" ,
447+ "handler" : "fc-handler" ,
448+ "middlewares" : []any {"fc-cors" },
449+ },
450+ },
451+ },
452+ },
453+ Triggers : map [string ]any {},
454+ }
455+
456+ logger := & mockLogger {}
457+ app := modular .NewStdApplication (modular .NewStdConfigProvider (nil ), logger )
458+ engine := NewStdEngine (app , logger )
459+ loadAllPlugins (t , engine )
460+ engine .RegisterWorkflowHandler (handlers .NewHTTPWorkflowHandler ())
461+
462+ if err := engine .BuildFromConfig (cfg ); err != nil {
463+ t .Fatalf ("BuildFromConfig failed: %v" , err )
464+ }
465+
466+ ctx := t .Context ()
467+ if err := engine .Start (ctx ); err != nil {
468+ t .Fatalf ("Engine start failed: %v" , err )
469+ }
470+ defer engine .Stop (context .Background ())
471+
472+ waitForServer (t , baseURL , 5 * time .Second )
473+ client := & http.Client {Timeout : 5 * time .Second }
474+
475+ // Subtest 1: Configurable allowedHeaders are reflected
476+ t .Run ("configurable_headers" , func (t * testing.T ) {
477+ req , _ := http .NewRequest ("GET" , baseURL + "/api/fc-test" , nil )
478+ req .Header .Set ("Origin" , "http://app.example.com" )
479+ resp , err := client .Do (req )
480+ if err != nil {
481+ t .Fatalf ("Request failed: %v" , err )
482+ }
483+ defer resp .Body .Close ()
484+
485+ acah := resp .Header .Get ("Access-Control-Allow-Headers" )
486+ want := "Authorization, Content-Type, X-CSRF-Token, X-Request-Id"
487+ if acah != want {
488+ t .Errorf ("Expected Access-Control-Allow-Headers %q, got %q" , want , acah )
489+ }
490+ })
491+
492+ // Subtest 2: allowCredentials sets the Credentials header
493+ t .Run ("allow_credentials" , func (t * testing.T ) {
494+ req , _ := http .NewRequest ("GET" , baseURL + "/api/fc-test" , nil )
495+ req .Header .Set ("Origin" , "https://trusted.io" )
496+ resp , err := client .Do (req )
497+ if err != nil {
498+ t .Fatalf ("Request failed: %v" , err )
499+ }
500+ defer resp .Body .Close ()
501+
502+ if resp .Header .Get ("Access-Control-Allow-Credentials" ) != "true" {
503+ t .Errorf ("Expected Access-Control-Allow-Credentials: true, got %q" , resp .Header .Get ("Access-Control-Allow-Credentials" ))
504+ }
505+ })
506+
507+ // Subtest 3: maxAge is set on responses
508+ t .Run ("max_age" , func (t * testing.T ) {
509+ req , _ := http .NewRequest ("GET" , baseURL + "/api/fc-test" , nil )
510+ req .Header .Set ("Origin" , "https://trusted.io" )
511+ resp , err := client .Do (req )
512+ if err != nil {
513+ t .Fatalf ("Request failed: %v" , err )
514+ }
515+ defer resp .Body .Close ()
516+
517+ if resp .Header .Get ("Access-Control-Max-Age" ) != "3600" {
518+ t .Errorf ("Expected Access-Control-Max-Age: 3600, got %q" , resp .Header .Get ("Access-Control-Max-Age" ))
519+ }
520+ })
521+
522+ // Subtest 4: Wildcard subdomain matching
523+ t .Run ("wildcard_subdomain" , func (t * testing.T ) {
524+ req , _ := http .NewRequest ("GET" , baseURL + "/api/fc-test" , nil )
525+ req .Header .Set ("Origin" , "http://admin.example.com" )
526+ resp , err := client .Do (req )
527+ if err != nil {
528+ t .Fatalf ("Request failed: %v" , err )
529+ }
530+ defer resp .Body .Close ()
531+
532+ acao := resp .Header .Get ("Access-Control-Allow-Origin" )
533+ if acao != "http://admin.example.com" {
534+ t .Errorf ("Expected Access-Control-Allow-Origin 'http://admin.example.com', got %q" , acao )
535+ }
536+ })
537+
538+ // Subtest 5: Wildcard subdomain does not match unrelated domains
539+ t .Run ("wildcard_subdomain_no_match" , func (t * testing.T ) {
540+ req , _ := http .NewRequest ("GET" , baseURL + "/api/fc-test" , nil )
541+ req .Header .Set ("Origin" , "http://evil.com" )
542+ resp , err := client .Do (req )
543+ if err != nil {
544+ t .Fatalf ("Request failed: %v" , err )
545+ }
546+ defer resp .Body .Close ()
547+
548+ acao := resp .Header .Get ("Access-Control-Allow-Origin" )
549+ if acao != "" {
550+ t .Errorf ("Expected no Access-Control-Allow-Origin for disallowed domain, got %q" , acao )
551+ }
552+ })
553+
554+ t .Log ("E2E Middleware CORS FullConfig: all new features verified" )
555+ }
556+
419557// TestE2E_Middleware_RequestID verifies the RequestID middleware adds an
420558// X-Request-ID header to every response, and preserves a client-supplied one.
421559func TestE2E_Middleware_RequestID (t * testing.T ) {
0 commit comments