@@ -309,3 +309,98 @@ def test__given_disabled_in_dev_with_correct_ack__then_allows_and_logs(
309309 assert any (
310310 "GATEWAY AUTH IS DISABLED" in record .message for record in caplog .records
311311 ), f"Expected critical auth-disabled banner, got { caplog .records !r} "
312+
313+
314+ class TestAuthConfiguredGuard :
315+ """Startup guard for required-or-partial auth misconfiguration."""
316+
317+ def test__given_auth_disabled__then_guard_noops (self , monkeypatch ):
318+ monkeypatch .setenv (auth_module .GATEWAY_AUTH_DISABLED_ENV , "1" )
319+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_ISSUER_ENV , raising = False )
320+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_AUDIENCE_ENV , raising = False )
321+
322+ auth_module .enforce_auth_configured_guard ()
323+
324+ def test__given_auth_optional_and_unset__then_guard_noops (self , monkeypatch ):
325+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_DISABLED_ENV , raising = False )
326+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_REQUIRED_ENV , raising = False )
327+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_ISSUER_ENV , raising = False )
328+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_AUDIENCE_ENV , raising = False )
329+
330+ auth_module .enforce_auth_configured_guard ()
331+
332+ def test__given_partial_auth_config__then_guard_raises (self , monkeypatch ):
333+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_DISABLED_ENV , raising = False )
334+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_REQUIRED_ENV , raising = False )
335+ monkeypatch .setenv (auth_module .GATEWAY_AUTH_ISSUER_ENV , "https://issuer.example/" )
336+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_AUDIENCE_ENV , raising = False )
337+
338+ with pytest .raises (auth_module .AuthMisconfiguredError ):
339+ auth_module .enforce_auth_configured_guard ()
340+
341+ def test__given_required_and_missing__then_guard_raises (self , monkeypatch ):
342+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_DISABLED_ENV , raising = False )
343+ monkeypatch .setenv (auth_module .GATEWAY_AUTH_REQUIRED_ENV , "1" )
344+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_ISSUER_ENV , raising = False )
345+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_AUDIENCE_ENV , raising = False )
346+
347+ with pytest .raises (auth_module .AuthMisconfiguredError ):
348+ auth_module .enforce_auth_configured_guard ()
349+
350+ def test__given_required_and_configured__then_guard_noops (self , monkeypatch ):
351+ monkeypatch .delenv (auth_module .GATEWAY_AUTH_DISABLED_ENV , raising = False )
352+ monkeypatch .setenv (auth_module .GATEWAY_AUTH_REQUIRED_ENV , "1" )
353+ monkeypatch .setenv (auth_module .GATEWAY_AUTH_ISSUER_ENV , "https://issuer.example/" )
354+ monkeypatch .setenv (auth_module .GATEWAY_AUTH_AUDIENCE_ENV , "aud" )
355+
356+ auth_module .enforce_auth_configured_guard ()
357+
358+
359+ class TestIssuerNormalization :
360+ """Issuer should be normalized to the trailing-slash Auth0 form."""
361+
362+ def test__given_issuer_without_slash__then_decoder_receives_normalized_value (
363+ self , monkeypatch
364+ ):
365+ monkeypatch .setenv (auth_module .GATEWAY_AUTH_ISSUER_ENV , "https://issuer.example" )
366+ monkeypatch .setenv (auth_module .GATEWAY_AUTH_AUDIENCE_ENV , "aud" )
367+ auth_module .reset_decoder_cache ()
368+
369+ captured = {}
370+
371+ def fake_builder (issuer , audience ):
372+ captured ["issuer" ] = issuer
373+ captured ["audience" ] = audience
374+ return object ()
375+
376+ monkeypatch .setattr (auth_module , "_build_decoder" , fake_builder )
377+
378+ auth_module ._get_decoder ()
379+
380+ assert captured == {
381+ "issuer" : "https://issuer.example/" ,
382+ "audience" : "aud" ,
383+ }
384+
385+ def test__given_issuer_with_slash__then_decoder_receives_unchanged_value (
386+ self , monkeypatch
387+ ):
388+ monkeypatch .setenv (auth_module .GATEWAY_AUTH_ISSUER_ENV , "https://issuer.example/" )
389+ monkeypatch .setenv (auth_module .GATEWAY_AUTH_AUDIENCE_ENV , "aud" )
390+ auth_module .reset_decoder_cache ()
391+
392+ captured = {}
393+
394+ def fake_builder (issuer , audience ):
395+ captured ["issuer" ] = issuer
396+ captured ["audience" ] = audience
397+ return object ()
398+
399+ monkeypatch .setattr (auth_module , "_build_decoder" , fake_builder )
400+
401+ auth_module ._get_decoder ()
402+
403+ assert captured == {
404+ "issuer" : "https://issuer.example/" ,
405+ "audience" : "aud" ,
406+ }
0 commit comments