From 6db5455762203d14ce2f2a01e0c1c81057dc7b50 Mon Sep 17 00:00:00 2001 From: Amir Zarrinkafsh Date: Thu, 19 Nov 2020 12:50:34 +1100 Subject: [PATCH] [CI] Collect coverage from frontend during integration tests (#1472) This change will allow us to collect frontend code coverage from our Selenium based integration tests. Given that the frontend is embedded into the Go binary and the integration tests run with a compiled binary in Docker this poses some issues with the instrumented code and the ability for it to run in this manner. To fix this we need to relax Authelia's CSP for the integration tests. This is achieved by setting the env variable `ENVIRONMENT` to `dev`. --- .buildkite/hooks/post-command | 7 +---- Dockerfile.coverage | 2 +- cmd/authelia-scripts/cmd_suites.go | 15 ++++++++++ internal/server/const.go | 3 ++ internal/server/index.go | 8 +++++- internal/server/server.go | 2 +- internal/suites/LDAP/configuration.yml | 1 + .../kube/authelia/configs/configuration.yml | 1 + .../example/kube/authelia/deployment.yml | 2 ++ internal/suites/webdriver.go | 28 ++++++++++++++++++- web/craco.config.js | 5 ++++ web/package.json | 5 +++- web/yarn.lock | 16 +++++++++++ 13 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 internal/server/const.go create mode 100644 web/craco.config.js diff --git a/.buildkite/hooks/post-command b/.buildkite/hooks/post-command index eaf47575..a843d66e 100755 --- a/.buildkite/hooks/post-command +++ b/.buildkite/hooks/post-command @@ -10,16 +10,11 @@ if [[ $BUILDKITE_PULL_REQUEST != "false" ]]; then fi if [[ ! $BUILDKITE_BRANCH =~ ^(v.*) ]] && [[ $BUILDKITE_COMMAND_EXIT_STATUS == 0 ]]; then - if [[ $BUILDKITE_LABEL == ":hammer_and_wrench: Unit Test" ]]; then + if [[ $BUILDKITE_LABEL == ":hammer_and_wrench: Unit Test" ]] || [[ $BUILDKITE_LABEL =~ ":selenium:" ]]; then echo "--- :codecov: Upload coverage reports" bash <(curl -s --connect-timeout 10 --retry 10 --retry-max-time 0 https://codecov.io/bash) -v -Z -c -f "coverage.txt" -F backend bash <(curl -s --connect-timeout 10 --retry 10 --retry-max-time 0 https://codecov.io/bash) -v -Z -c -F frontend fi - - if [[ $BUILDKITE_LABEL =~ ":selenium:" ]]; then - echo "--- :codecov: Upload coverage reports" - bash <(curl -s --connect-timeout 10 --retry 10 --retry-max-time 0 https://codecov.io/bash) -v -Z -c -f "coverage.txt" -F backend - fi fi if [[ $BUILDKITE_LABEL =~ ":selenium:" ]] || [[ $BUILDKITE_LABEL =~ ":docker: Build Image" ]]; then diff --git a/Dockerfile.coverage b/Dockerfile.coverage index f7af796f..8b6307bf 100644 --- a/Dockerfile.coverage +++ b/Dockerfile.coverage @@ -7,7 +7,7 @@ WORKDIR /node/src/app COPY web . # Install the dependencies and build -RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn build +RUN yarn install --frozen-lockfile && INLINE_RUNTIME_CHUNK=false yarn coverage # ======================================= # ===== Build image for the backend ===== diff --git a/cmd/authelia-scripts/cmd_suites.go b/cmd/authelia-scripts/cmd_suites.go index aa284285..93d01f0e 100644 --- a/cmd/authelia-scripts/cmd_suites.go +++ b/cmd/authelia-scripts/cmd_suites.go @@ -138,6 +138,21 @@ func runSuiteSetupTeardown(command string, suite string) error { s := suites.GlobalRegistry.Get(selectedSuite) + if command == "teardown" { + if _, err := os.Stat("../../web/.nyc_output"); err == nil { + log.Infof("Generating frontend coverage reports for suite %s...", suite) + + cmd := utils.Command("yarn", "report") + cmd.Dir = "web" + cmd.Env = os.Environ() + + err := cmd.Run() + if err != nil { + log.Fatal(err) + } + } + } + cmd := utils.CommandWithStdout("go", "run", "cmd/authelia-suites/main.go", command, selectedSuite) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr diff --git a/internal/server/const.go b/internal/server/const.go new file mode 100644 index 00000000..d092ebf7 --- /dev/null +++ b/internal/server/const.go @@ -0,0 +1,3 @@ +package server + +const dev = "dev" diff --git a/internal/server/index.go b/internal/server/index.go index fbf09e9c..ad4f36ac 100644 --- a/internal/server/index.go +++ b/internal/server/index.go @@ -3,6 +3,7 @@ package server import ( "fmt" "io/ioutil" + "os" "text/template" "github.com/valyala/fasthttp" @@ -36,7 +37,12 @@ func ServeIndex(publicDir, base, rememberMe, resetPassword string) fasthttp.Requ nonce := utils.RandomString(32, alphaNumericRunes) ctx.SetContentType("text/html; charset=utf-8") - ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self'; object-src 'none'; style-src 'self' 'nonce-%s'", nonce)) + + if os.Getenv("ENVIRONMENT") == dev { + ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self' 'unsafe-eval'; object-src 'none'; style-src 'self' 'nonce-%s'", nonce)) + } else { + ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self'; object-src 'none'; style-src 'self' 'nonce-%s'", nonce)) + } err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, CSPNonce, RememberMe, ResetPassword string }{Base: base, CSPNonce: nonce, RememberMe: rememberMe, ResetPassword: resetPassword}) if err != nil { diff --git a/internal/server/server.go b/internal/server/server.go index 7225456c..f2921b1f 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -99,7 +99,7 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi // Configure DUO api endpoint only if configuration exists. if configuration.DuoAPI != nil { var duoAPI duo.API - if os.Getenv("ENVIRONMENT") == "dev" { + if os.Getenv("ENVIRONMENT") == dev { duoAPI = duo.NewDuoAPI(duoapi.NewDuoApi( configuration.DuoAPI.IntegrationKey, configuration.DuoAPI.SecretKey, diff --git a/internal/suites/LDAP/configuration.yml b/internal/suites/LDAP/configuration.yml index 80645ee4..5e20f7e3 100644 --- a/internal/suites/LDAP/configuration.yml +++ b/internal/suites/LDAP/configuration.yml @@ -24,6 +24,7 @@ authentication_backend: groups_filter: (&(member={dn})(objectclass=groupOfNames)) group_name_attribute: cn mail_attribute: mail + display_name_attribute: displayName user: cn=admin,dc=example,dc=com password: password diff --git a/internal/suites/example/kube/authelia/configs/configuration.yml b/internal/suites/example/kube/authelia/configs/configuration.yml index ce9e1fa8..6be69cd9 100644 --- a/internal/suites/example/kube/authelia/configs/configuration.yml +++ b/internal/suites/example/kube/authelia/configs/configuration.yml @@ -22,6 +22,7 @@ authentication_backend: groups_filter: (&(member={dn})(objectclass=groupOfNames)) group_name_attribute: cn mail_attribute: mail + display_name_attribute: displayName user: cn=admin,dc=example,dc=com access_control: diff --git a/internal/suites/example/kube/authelia/deployment.yml b/internal/suites/example/kube/authelia/deployment.yml index 2945205f..5530fc3d 100644 --- a/internal/suites/example/kube/authelia/deployment.yml +++ b/internal/suites/example/kube/authelia/deployment.yml @@ -40,6 +40,8 @@ spec: value: /app/secrets/session - name: AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE value: /app/secrets/sql_password + - name: ENVIRONMENT + value: dev volumes: - name: config-volume configMap: diff --git a/internal/suites/webdriver.go b/internal/suites/webdriver.go index 40896eeb..25fc8b02 100644 --- a/internal/suites/webdriver.go +++ b/internal/suites/webdriver.go @@ -2,12 +2,15 @@ package suites import ( "context" + "encoding/json" "errors" "fmt" + "io/ioutil" "os" "strconv" "strings" "testing" + "time" "github.com/stretchr/testify/require" "github.com/tebeka/selenium" @@ -82,8 +85,31 @@ func StartWebDriver() (*WebDriverSession, error) { // Stop stop the selenium session. func (wds *WebDriverSession) Stop() error { - err := wds.WebDriver.Quit() + var coverage map[string]interface{} + coverageDir := "../../web/.nyc_output" + time := time.Now() + + resp, err := wds.WebDriver.ExecuteScriptRaw("return JSON.stringify(window.__coverage__)", nil) + if err != nil { + return err + } + + err = json.Unmarshal(resp, &coverage) + if err != nil { + return err + } + + coverageData := fmt.Sprintf("%s", coverage["value"]) + + _ = os.MkdirAll(coverageDir, 0775) + + err = ioutil.WriteFile(fmt.Sprintf("%s/coverage-%d.json", coverageDir, time.Unix()), []byte(coverageData), 0664) //nolint:gosec + if err != nil { + return err + } + + err = wds.WebDriver.Quit() if err != nil { return err } diff --git a/web/craco.config.js b/web/craco.config.js new file mode 100644 index 00000000..67c5744e --- /dev/null +++ b/web/craco.config.js @@ -0,0 +1,5 @@ +module.exports = { + babel: { + plugins: [ "babel-plugin-istanbul" ] + } +}; \ No newline at end of file diff --git a/web/package.json b/web/package.json index c13467b9..1eb0e0da 100644 --- a/web/package.json +++ b/web/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@craco/craco": "^5.8.0", "@fortawesome/fontawesome-svg-core": "^1.2.32", "@fortawesome/free-regular-svg-icons": "^5.15.1", "@fortawesome/free-solid-svg-icons": "^5.15.1", @@ -40,9 +41,11 @@ "u2f-api": "^1.1.1" }, "scripts": { - "start": "react-scripts start", + "start": "craco start", "build": "react-scripts build", + "coverage": "craco build", "test": "react-scripts test --coverage --no-cache", + "report": "nyc report -r clover -r json -r lcov -r text", "eject": "react-scripts eject" }, "eslintConfig": { diff --git a/web/yarn.lock b/web/yarn.lock index fe50ce22..6e1cb5ca 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1159,6 +1159,15 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@craco/craco@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@craco/craco/-/craco-5.8.0.tgz#2a0f551290a5eab353b615de4d7093dd83785777" + integrity sha512-4rhusETLD7rJ195GxOK9VmVdv/VD4jawFxc9hcQ9TrZ3/9ny+qwc0uW+08qu9GYwEF9Eb9meSeSvpWjaqdDr1Q== + dependencies: + cross-spawn "^7.0.0" + lodash "^4.17.15" + webpack-merge "^4.2.2" + "@csstools/convert-colors@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" @@ -11979,6 +11988,13 @@ webpack-manifest-plugin@2.2.0: object.entries "^1.1.0" tapable "^1.0.0" +webpack-merge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" + integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== + dependencies: + lodash "^4.17.15" + webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"