122 lines
6.7 KiB
Gherkin
122 lines
6.7 KiB
Gherkin
Feature: Runtime settings are read once at startup
|
|
|
|
As a maintainer of RenewCMS
|
|
I want the environment, CORS origins, public URL, upload dir and SMTP
|
|
settings to be read from the process environment exactly once, in the
|
|
composition root, and handed down explicitly from there
|
|
So that no handler, router or repository reads an environment variable at
|
|
request or construction time, and changing the process environment after
|
|
startup can never change already-built behaviour
|
|
|
|
Background:
|
|
Given before this slice, os.Getenv is called outside cmd/server/main.go
|
|
in four places: api/router.go's enableLoggingIfDev reads ENVIRONMENT
|
|
on every router build, api/router.go's enableCorsIfConfigured reads
|
|
CORS_ALLOWED_ORIGINS on every router build,
|
|
api/handlers/auth.shouldCookieBeSecure reads ENVIRONMENT on every
|
|
Login/Register/Logout call, internal/infrastructure/mailer.MailRepository.Send
|
|
reads SMTP_LOGIN, SMTP_SENDER, SMTP_PASSWORD, SMTP_HOST and SMTP_PORT
|
|
on every call, and internal/infrastructure/persistence.ImageRepository.Create
|
|
and .Delete read UPLOAD_DIR on every call
|
|
And this slice introduces internal/infrastructure/config.Settings, a
|
|
plain struct with fields Environment, CorsAllowedOrigins ([]string),
|
|
PublicURL, UploadDir, JWTSecret, DBFile, Port and an Smtp sub-struct
|
|
(Login, Sender, Password, Host, Port)
|
|
And cmd/server/main.go's initEnvVariables still loads the .env file and
|
|
applies the same defaults and required-var checks as before, then
|
|
builds exactly one config.Settings value from the process environment,
|
|
before api.InitContainer or api.InitRoutes is ever called
|
|
And api.InitContainer and api.InitRoutes now take that config.Settings
|
|
value, and every downstream constructor receives its own slice of it
|
|
explicitly instead of reading os.Getenv: auth.NewHandler receives
|
|
whether the environment is production, mailer.NewMailRepository
|
|
receives the Smtp settings, persistence.NewImageRepository receives
|
|
the upload dir, and the router's logging and CORS setup receive the
|
|
environment string and the CORS origins list as parameters
|
|
And after this slice, os.Getenv (and os.LookupEnv) for ENVIRONMENT,
|
|
CORS_ALLOWED_ORIGINS, SMTP_LOGIN, SMTP_SENDER, SMTP_PASSWORD,
|
|
SMTP_HOST, SMTP_PORT and UPLOAD_DIR appears only inside
|
|
cmd/server/main.go and internal/infrastructure/config
|
|
And a component built from a config.Settings value keeps behaving
|
|
according to the values it was built with even if the process
|
|
environment changes afterwards, because it holds the captured values,
|
|
not the environment
|
|
And GET /healthz is mounted directly on the outer API router
|
|
(api/router.go), not under /v1, so the CORS middleware wraps it the
|
|
same way it wraps every route
|
|
|
|
Scenario Outline: The jwt cookie's Secure flag follows the injected environment, not the process environment
|
|
Given a config.Settings value with Environment "<injected environment>"
|
|
And the auth handler is built from that settings value
|
|
And the process environment variable ENVIRONMENT is set to "<process environment>"
|
|
When the client sends POST /v1/auth/login with valid credentials for an
|
|
existing, verified user
|
|
Then the response's Set-Cookie header for the "jwt" cookie has its
|
|
Secure attribute set to <secure expected>
|
|
|
|
Examples:
|
|
| injected environment | process environment | secure expected |
|
|
| production | production | true |
|
|
| production | development | true |
|
|
| development | production | false |
|
|
| development | development | false |
|
|
|
|
Scenario: CORS allows a request from an injected allowed origin
|
|
Given a config.Settings value with CorsAllowedOrigins
|
|
["https://cms.example.com"]
|
|
And the API router is built from that settings value
|
|
When the client sends GET /healthz with header
|
|
Origin: https://cms.example.com
|
|
Then the response has header Access-Control-Allow-Origin:
|
|
https://cms.example.com
|
|
|
|
Scenario: CORS does not allow a request from an origin outside the injected list
|
|
Given a config.Settings value with CorsAllowedOrigins
|
|
["https://cms.example.com"]
|
|
And the API router is built from that settings value
|
|
When the client sends GET /healthz with header
|
|
Origin: https://attacker.example.com
|
|
Then the response has no Access-Control-Allow-Origin header
|
|
|
|
Scenario: An empty injected origins list disables CORS entirely
|
|
Given a config.Settings value with CorsAllowedOrigins as an empty list
|
|
And the API router is built from that settings value
|
|
When the client sends GET /healthz with header
|
|
Origin: https://cms.example.com
|
|
Then the response has no Access-Control-Allow-Origin header
|
|
|
|
Scenario: An uploaded image is stored under the injected upload directory
|
|
Given a config.Settings value with UploadDir pointing at a fresh
|
|
directory created for this scenario
|
|
And the image repository is built from that settings value, over a
|
|
fresh in-memory database
|
|
And the process environment variable UPLOAD_DIR is unset or points at a
|
|
different directory
|
|
When internal/infrastructure/persistence.ImageRepository.Create is
|
|
called with a PNG file
|
|
Then the image file is written inside the injected upload directory,
|
|
not inside the process environment's UPLOAD_DIR
|
|
And the returned image's Path still starts with "/static/uploadedImages/"
|
|
|
|
Scenario: Changing the process environment after construction does not change already-built behaviour
|
|
Given a config.Settings value with Environment "production"
|
|
And the auth handler is built from that settings value
|
|
When the process environment variable ENVIRONMENT is then changed to
|
|
"development"
|
|
And the client sends POST /v1/auth/login with valid credentials for an
|
|
existing, verified user
|
|
Then the response's Set-Cookie header for the "jwt" cookie still has its
|
|
Secure attribute set to true
|
|
|
|
Scenario: The mailer is constructed from injected SMTP settings, not the environment
|
|
Given a config.Settings value with Smtp.Login "smtp-login@example.com",
|
|
Smtp.Sender "notify@example.com", Smtp.Host "smtp.example.com" and
|
|
Smtp.Port 587
|
|
And internal/infrastructure/mailer.NewMailRepository is called with that
|
|
Smtp settings value and no SMTP_* environment variable set
|
|
Then constructing the MailRepository does not read any SMTP_*
|
|
environment variable
|
|
And the maintainer verifies by manual test (see QA procedure) that an
|
|
actual send still uses those injected values, since a real SMTP
|
|
dialog is never exercised by the automated suite
|