[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`.
pull/1473/head
Amir Zarrinkafsh 2020-11-19 12:50:34 +11:00 committed by GitHub
parent ec0af02aa3
commit 6db5455762
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 84 additions and 11 deletions

View File

@ -10,16 +10,11 @@ if [[ $BUILDKITE_PULL_REQUEST != "false" ]]; then
fi fi
if [[ ! $BUILDKITE_BRANCH =~ ^(v.*) ]] && [[ $BUILDKITE_COMMAND_EXIT_STATUS == 0 ]]; then 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" 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 "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 bash <(curl -s --connect-timeout 10 --retry 10 --retry-max-time 0 https://codecov.io/bash) -v -Z -c -F frontend
fi 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 fi
if [[ $BUILDKITE_LABEL =~ ":selenium:" ]] || [[ $BUILDKITE_LABEL =~ ":docker: Build Image" ]]; then if [[ $BUILDKITE_LABEL =~ ":selenium:" ]] || [[ $BUILDKITE_LABEL =~ ":docker: Build Image" ]]; then

View File

@ -7,7 +7,7 @@ WORKDIR /node/src/app
COPY web . COPY web .
# Install the dependencies and build # 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 ===== # ===== Build image for the backend =====

View File

@ -138,6 +138,21 @@ func runSuiteSetupTeardown(command string, suite string) error {
s := suites.GlobalRegistry.Get(selectedSuite) 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 := utils.CommandWithStdout("go", "run", "cmd/authelia-suites/main.go", command, selectedSuite)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr

View File

@ -0,0 +1,3 @@
package server
const dev = "dev"

View File

@ -3,6 +3,7 @@ package server
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"text/template" "text/template"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
@ -36,7 +37,12 @@ func ServeIndex(publicDir, base, rememberMe, resetPassword string) fasthttp.Requ
nonce := utils.RandomString(32, alphaNumericRunes) nonce := utils.RandomString(32, alphaNumericRunes)
ctx.SetContentType("text/html; charset=utf-8") 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}) err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, CSPNonce, RememberMe, ResetPassword string }{Base: base, CSPNonce: nonce, RememberMe: rememberMe, ResetPassword: resetPassword})
if err != nil { if err != nil {

View File

@ -99,7 +99,7 @@ func StartServer(configuration schema.Configuration, providers middlewares.Provi
// Configure DUO api endpoint only if configuration exists. // Configure DUO api endpoint only if configuration exists.
if configuration.DuoAPI != nil { if configuration.DuoAPI != nil {
var duoAPI duo.API var duoAPI duo.API
if os.Getenv("ENVIRONMENT") == "dev" { if os.Getenv("ENVIRONMENT") == dev {
duoAPI = duo.NewDuoAPI(duoapi.NewDuoApi( duoAPI = duo.NewDuoAPI(duoapi.NewDuoApi(
configuration.DuoAPI.IntegrationKey, configuration.DuoAPI.IntegrationKey,
configuration.DuoAPI.SecretKey, configuration.DuoAPI.SecretKey,

View File

@ -24,6 +24,7 @@ authentication_backend:
groups_filter: (&(member={dn})(objectclass=groupOfNames)) groups_filter: (&(member={dn})(objectclass=groupOfNames))
group_name_attribute: cn group_name_attribute: cn
mail_attribute: mail mail_attribute: mail
display_name_attribute: displayName
user: cn=admin,dc=example,dc=com user: cn=admin,dc=example,dc=com
password: password password: password

View File

@ -22,6 +22,7 @@ authentication_backend:
groups_filter: (&(member={dn})(objectclass=groupOfNames)) groups_filter: (&(member={dn})(objectclass=groupOfNames))
group_name_attribute: cn group_name_attribute: cn
mail_attribute: mail mail_attribute: mail
display_name_attribute: displayName
user: cn=admin,dc=example,dc=com user: cn=admin,dc=example,dc=com
access_control: access_control:

View File

@ -40,6 +40,8 @@ spec:
value: /app/secrets/session value: /app/secrets/session
- name: AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE - name: AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE
value: /app/secrets/sql_password value: /app/secrets/sql_password
- name: ENVIRONMENT
value: dev
volumes: volumes:
- name: config-volume - name: config-volume
configMap: configMap:

View File

@ -2,12 +2,15 @@ package suites
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
"time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tebeka/selenium" "github.com/tebeka/selenium"
@ -82,8 +85,31 @@ func StartWebDriver() (*WebDriverSession, error) {
// Stop stop the selenium session. // Stop stop the selenium session.
func (wds *WebDriverSession) Stop() error { 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 { if err != nil {
return err return err
} }

View File

@ -0,0 +1,5 @@
module.exports = {
babel: {
plugins: [ "babel-plugin-istanbul" ]
}
};

View File

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@craco/craco": "^5.8.0",
"@fortawesome/fontawesome-svg-core": "^1.2.32", "@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-regular-svg-icons": "^5.15.1", "@fortawesome/free-regular-svg-icons": "^5.15.1",
"@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/free-solid-svg-icons": "^5.15.1",
@ -40,9 +41,11 @@
"u2f-api": "^1.1.1" "u2f-api": "^1.1.1"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "craco start",
"build": "react-scripts build", "build": "react-scripts build",
"coverage": "craco build",
"test": "react-scripts test --coverage --no-cache", "test": "react-scripts test --coverage --no-cache",
"report": "nyc report -r clover -r json -r lcov -r text",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"eslintConfig": { "eslintConfig": {

View File

@ -1159,6 +1159,15 @@
exec-sh "^0.3.2" exec-sh "^0.3.2"
minimist "^1.2.0" 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": "@csstools/convert-colors@^1.4.0":
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" 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" object.entries "^1.1.0"
tapable "^1.0.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: 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" version "1.4.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"