Start testing migrations and seeds on CI (#1359)

Ref: #1316

This additionally adds static-analysis and type-check steps to `test`
workflow. Even though they run in a separate workflow I feel like we
might want to remove dialyzer from pre-commit hook as it sometimes takes
a lot of time, especially if you do checkout between branches that
change deps often and slows down when you commit rapidly.
This commit is contained in:
Andrew Dryga
2023-01-22 22:01:58 -06:00
committed by GitHub
parent f0ed59ad2d
commit 8a02629163
12 changed files with 287 additions and 55 deletions

View File

@@ -39,7 +39,7 @@ jobs:
- uses: actions/cache@v3
name: Elixir Deps Cache
env:
cache-name: cache-elixir-deps
cache-name: cache-elixir-deps-${{ env.MIX_ENV }}
with:
path: deps
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
@@ -48,7 +48,7 @@ jobs:
- uses: actions/cache@v3
name: Elixir Build Cache
env:
cache-name: cache-elixir-build
cache-name: cache-elixir-build-${{ env.MIX_ENV }}
with:
path: _build
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
@@ -67,7 +67,7 @@ jobs:
E2E_MAX_WAIT_SECONDS: 20
run: |
# XXX: This can fail when coveralls is down
mix coveralls.github --umbrella
mix coveralls.github --umbrella --warnings-as-errors
- name: Test Report
uses: dorny/test-reporter@v1
if: success() || failure()
@@ -75,6 +75,188 @@ jobs:
name: Elixir Unit Test Report
path: _build/test/lib/*/test-junit-report.xml
reporter: java-junit
type-check:
runs-on: ubuntu-latest
env:
MIX_ENV: dev
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: '25'
elixir-version: '1.14'
- uses: actions/cache@v3
name: Elixir Deps Cache
env:
cache-name: cache-elixir-deps-${{ env.MIX_ENV }}
with:
path: deps
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.cache-name }}-
- uses: actions/cache@v3
name: Elixir Build Cache
env:
cache-name: cache-elixir-build-${{ env.MIX_ENV }}
with:
path: _build
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
- name: Install Dependencies
run: mix deps.get --only $MIX_ENV
- name: Compile Dependencies
run: mix deps.compile --skip-umbrella-children
- name: Compile Application
run: mix compile
# Don't cache PLTs based on mix.lock hash, as Dialyzer can incrementally update even old ones
# Cache key based on Elixir & Erlang version (also useful when running in matrix)
- name: Restore PLT cache
uses: actions/cache@v3
env:
cache-name: cache-erlang-plt-${{ env.MIX_ENV }}
with:
key: ${{ runner.os }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ steps.setup-beam.outputs.otp-version }}-plt
restore-keys: |
${{ runner.os }}-${{ steps.setup-beam.outputs.elixir-version }}-${{ steps.setup-beam.outputs.otp-version }}-plt
path: priv/plts
- name: Create PLTs
if: steps.plt_cache.outputs.cache-hit != 'true'
run: mix dialyzer --plt
- name: Run Dialyzer
run: mix dialyzer --format dialyxir
static-analysis:
runs-on: ubuntu-latest
env:
MIX_ENV: test
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: '25'
elixir-version: '1.14'
- uses: actions/cache@v3
name: Elixir Deps Cache
env:
cache-name: cache-elixir-deps-${{ env.MIX_ENV }}
with:
path: deps
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.cache-name }}-
- uses: actions/cache@v3
name: Elixir Build Cache
env:
cache-name: cache-elixir-build-${{ env.MIX_ENV }}
with:
path: _build
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
- name: Install Dependencies
run: mix deps.get --only $MIX_ENV
- name: Compile Dependencies
run: mix deps.compile --skip-umbrella-children
- name: Compile Application
run: mix compile --force --warnings-as-errors
- name: Check Formatting
run: mix format --check-formatted
- name: Run Credo
run: mix credo --strict
migrations-and-seed-test:
runs-on: ubuntu-latest
env:
MIX_ENV: dev
POSTGRES_HOST: localhost
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FZ_VPN_WG_ADAPTER: FzVpn.Interface.WGAdapter.Sandbox
services:
postgres:
image: postgres:15
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Install package dependencies
run: |
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc &>/dev/null
sudo apt update
sudo apt-get install -q -y \
net-tools \
wireguard \
postgresql-client
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: '25'
elixir-version: '1.14'
- uses: actions/cache@v3
name: Elixir Deps Cache
env:
cache-name: cache-elixir-deps-${{ env.MIX_ENV }}-${{ env.MIX_ENV }}
with:
path: deps
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.cache-name }}-
- uses: actions/cache@v3
name: Elixir Build Cache
env:
cache-name: cache-elixir-build-${{ env.MIX_ENV }}
with:
path: _build
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
- name: Install Dependencies
run: mix deps.get --only $MIX_ENV
- name: Compile
run: mix compile
- name: Download main branch DB dump
id: download-artifact
uses: dawidd6/action-download-artifact@v2
if: "!contains(github.ref, 'main')"
with:
branch: master
name: db-dump
path: apps/fz_http/priv/repo/
search_artifacts: true
workflow_conclusion: completed
if_no_artifact_found: fail
- name: Create Database
run: |
mix ecto.create
- name: Restore DB dump
if: "!contains(github.ref, 'main')"
env:
PGPASSWORD: postgres
run: |
mix ecto.load
- name: Run new migrations
run: |
mix ecto.migrate
- name: Dump DB
if: "contains(github.ref, 'main')"
env:
PGPASSWORD: postgres
run: |
pg_dump firezone_dev \
-U postgres -h localhost \
--file apps/fz_http/priv/repo/structure.sql \
--no-acl \
--no-owner
- name: Upload main branch DB dump
if: "contains(github.ref, 'main')"
uses: actions/upload-artifact@v3
with:
name: db-dump
path: apps/fz_http/priv/repo/structure.sql
- name: Run Seed
run: mix ecto.seed
acceptance-test:
runs-on: ubuntu-latest
env:
@@ -110,11 +292,8 @@ jobs:
--cap-add=IPC_LOCK
steps:
- uses: nanasess/setup-chromedriver@v1
with:
chromedriver-version: '108.0.5359.71'
- run: |
export DISPLAY=:99
chromedriver --url-base=/wd/hub &
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
- name: Install package dependencies
run: |
@@ -129,7 +308,7 @@ jobs:
- uses: actions/cache@v3
name: Elixir Deps Cache
env:
cache-name: cache-elixir-deps
cache-name: cache-elixir-deps-${{ env.MIX_ENV }}-${{ env.MIX_ENV }}
with:
path: deps
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
@@ -138,21 +317,21 @@ jobs:
- uses: actions/cache@v3
name: Elixir Build Cache
env:
cache-name: cache-elixir-build
cache-name: cache-elixir-build-${{ env.MIX_ENV }}
with:
path: _build
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
- uses: actions/cache@v3
name: Yarn Deps Cache
env:
cache-name: cache-yarn-build
cache-name: cache-yarn-build-${{ env.MIX_ENV }}
with:
path: apps/fz_http/assets/node_modules
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
- uses: actions/cache@v3
name: Assets Cache
env:
cache-name: cache-assets-build
cache-name: cache-assets-build-${{ env.MIX_ENV }}
with:
path: apps/fz_http/priv/static/dist
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
@@ -174,17 +353,20 @@ jobs:
run: |
mix ecto.create
mix ecto.migrate
- name: Run Tests and Upload Coverage Report
- name: Run Acceptance Tests
env:
MIX_TEST_PARTITION: ${{ matrix.MIX_TEST_PARTITION }}
E2E_MAX_WAIT_SECONDS: 20
E2E_MAX_WAIT_SECONDS: 5
run: |
mix test --only acceptance:true \
--partitions=${{ env.MIX_TEST_PARTITIONS }} \
--no-compile \
--no-archives-check \
--no-deps-check \
|| mix test --failed
|| pkill -f chromedriver \
|| mix test --only acceptance:true --failed \
|| pkill -f chromedriver \
|| mix test --only acceptance:true --failed
- name: Save Screenshots
if: always()
uses: actions/upload-artifact@v3

View File

@@ -5,10 +5,9 @@ defmodule FzHttp.Config do
"""
if Mix.env() != :test do
defdelegate put_env(app, key, value), to: Application
defdelegate fetch_env!(app, key), to: Application
else
def put_env(app \\ :fz_http, key, value) do
def put_env_override(app \\ :fz_http, key, value) do
Process.put(key_function(app, key), value)
:ok
end

View File

@@ -60,7 +60,7 @@ defmodule FzHttp.SAML.StartProxy do
|> set_service_provider()
|> set_identity_providers(providers)
FzHttp.Config.put_env(:samly, Samly.Provider, samly_configs)
Application.put_env(:samly, Samly.Provider, samly_configs)
Samly.Provider.refresh_providers()
end
end

View File

@@ -13,9 +13,48 @@
alias FzHttp.{
ConnectivityChecks,
Devices,
Users
Users,
ApiTokens,
Rules,
MFA
}
{:ok, unprivileged_user1} =
Users.create_unprivileged_user(%{
email: "firezone-unprivileged-1@localhost"
})
{:ok, _device} =
Devices.create_device(%{
user_id: unprivileged_user1.id,
name: "My Device",
description: "foo bar",
preshared_key: "27eCDMVRVFfMVS5Rfnn9n7as4M6MemGY/oghmdrwX2E=",
public_key: "4Fo+SBnDJ6hi8qzPt3nWLwgjCVwvpjHL35qJeatKwEc=",
remote_ip: %Postgrex.INET{address: {127, 5, 0, 1}},
rx_bytes: 123_917_823,
tx_bytes: 1_934_475_211_087_234
})
{:ok, mfa_user} =
Users.create_unprivileged_user(%{
email: "firezone-mfa@localhost",
password: "firezone1234",
password_confirmation: "firezone1234"
})
secret = NimbleTOTP.secret()
MFA.create_method(
%{
name: "Google Authenticator",
type: :totp,
payload: %{"secret" => Base.encode64(secret)},
code: NimbleTOTP.verification_code(secret)
},
mfa_user.id
)
{:ok, user} =
Users.create_admin_user(%{
email: "firezone@localhost",
@@ -23,6 +62,10 @@ alias FzHttp.{
password_confirmation: "firezone1234"
})
{:ok, _api_token} = ApiTokens.create_user_api_token(user, %{"expires_in" => 5})
{:ok, _api_token} = ApiTokens.create_user_api_token(user, %{"expires_in" => 30})
{:ok, _api_token} = ApiTokens.create_user_api_token(user, %{"expires_in" => 1})
{:ok, _device} =
Devices.create_device(%{
user_id: user.id,
@@ -150,3 +193,30 @@ alias FzHttp.{
response_code: 400,
url: "https://ping-dev.firez.one/0.20.0"
})
Rules.create_rule(%{
destination: "10.0.0.0/24",
port_type: :tcp,
port_range: "100-200"
})
Rules.create_rule(%{
destination: "1.2.3.4"
})
FzHttp.Configurations.put!(
:openid_connect_providers,
[
%{
"id" => "vault",
"discovery_document_uri" => "https://common.auth0.com/.well-known/openid-configuration",
"client_id" => "CLIENT_ID",
"client_secret" => "CLIENT_SECRET",
"redirect_uri" => "http://localhost:13000/auth/oidc/vault/callback/",
"response_type" => "code",
"scope" => "openid email offline_access",
"label" => "OIDC Vault",
"auto_create_users" => true
}
]
)

View File

@@ -56,7 +56,7 @@ defmodule FzHttp.DevicesTest do
}
test "prevents creating more than max_devices_per_user", %{device: device} do
FzHttp.Config.put_env(:max_devices_per_user, 1)
FzHttp.Config.put_env_override(:max_devices_per_user, 1)
assert {:error,
%Ecto.Changeset{
@@ -83,7 +83,7 @@ defmodule FzHttp.DevicesTest do
end
test "soft limit max network range for IPv6", %{device: device} do
FzHttp.Config.put_env(:wireguard_ipv6_network, "fd00::/20")
FzHttp.Config.put_env_override(:wireguard_ipv6_network, "fd00::/20")
attrs = %{@device_attrs | ipv4: nil, ipv6: nil, user_id: device.user_id}
assert {:ok, _device} = Devices.create_device(attrs)
end
@@ -91,7 +91,7 @@ defmodule FzHttp.DevicesTest do
test "returns error when device IP can't be assigned due to CIDR pool exhaustion", %{
device: device
} do
FzHttp.Config.put_env(:wireguard_ipv4_network, "10.3.2.0/30")
FzHttp.Config.put_env_override(:wireguard_ipv4_network, "10.3.2.0/30")
attrs = %{@device_attrs | ipv4: nil, ipv6: nil, user_id: device.user_id}
assert {:ok, _device} = Devices.create_device(attrs)

View File

@@ -4,8 +4,8 @@ defmodule FzHttp.RulesTest do
alias FzHttp.Rules
setup do
FzHttp.Config.put_env(:wireguard_ipv4_network, "100.64.0.0/10")
FzHttp.Config.put_env(:wireguard_ipv6_network, "fd00::0/106")
FzHttp.Config.put_env_override(:wireguard_ipv4_network, "100.64.0.0/10")
FzHttp.Config.put_env_override(:wireguard_ipv6_network, "fd00::0/106")
:ok
end

View File

@@ -115,7 +115,7 @@ defmodule FzHttp.TelemetryTest do
describe "database" do
test "local hostname" do
FzHttp.Config.put_env(:fz_http, FzHttp.Repo, hostname: "localhost")
FzHttp.Config.put_env_override(:fz_http, FzHttp.Repo, hostname: "localhost")
ping_data = Telemetry.ping_data()
@@ -123,7 +123,7 @@ defmodule FzHttp.TelemetryTest do
end
test "local url" do
FzHttp.Config.put_env(:fz_http, FzHttp.Repo, url: "postgres://127.0.0.1")
FzHttp.Config.put_env_override(:fz_http, FzHttp.Repo, url: "postgres://127.0.0.1")
ping_data = Telemetry.ping_data()
@@ -131,7 +131,7 @@ defmodule FzHttp.TelemetryTest do
end
test "external hostname" do
FzHttp.Config.put_env(:fz_http, FzHttp.Repo, hostname: "firezone.dev")
FzHttp.Config.put_env_override(:fz_http, FzHttp.Repo, hostname: "firezone.dev")
ping_data = Telemetry.ping_data()
@@ -139,7 +139,7 @@ defmodule FzHttp.TelemetryTest do
end
test "external url" do
FzHttp.Config.put_env(:fz_http, FzHttp.Repo, url: "postgres://firezone.dev")
FzHttp.Config.put_env_override(:fz_http, FzHttp.Repo, url: "postgres://firezone.dev")
ping_data = Telemetry.ping_data()
@@ -149,7 +149,7 @@ defmodule FzHttp.TelemetryTest do
describe "email" do
test "outbound set" do
FzHttp.Config.put_env(:fz_http, FzHttpWeb.Mailer,
FzHttp.Config.put_env_override(:fz_http, FzHttpWeb.Mailer,
adapter: Swoosh.Adapters.NoopAdapter,
from_email: "test@firezone.dev"
)
@@ -160,7 +160,7 @@ defmodule FzHttp.TelemetryTest do
end
test "outbound unset" do
FzHttp.Config.put_env(:fz_http, FzHttpWeb.Mailer,
FzHttp.Config.put_env_override(:fz_http, FzHttpWeb.Mailer,
adapter: SwooshAdapters.NoopAdapter,
from_email: nil
)

View File

@@ -478,7 +478,6 @@ defmodule FzHttpWeb.Acceptance.AdminTest do
|> assert_el(Query.text("Updated successfully."))
|> assert_el(Query.text("foo-bar-buz"))
|> assert_el(Query.text("Sneaky ID"))
|> assert_el(Query.text("http://localhost:4002/autX/saml#foo"))
assert [saml_identity_provider] = FzHttp.Configurations.get!(:saml_identity_providers)
@@ -495,21 +494,6 @@ defmodule FzHttpWeb.Acceptance.AdminTest do
auto_create_users: true
}
assert FzHttp.Config.fetch_env!(:samly, Samly.Provider) == [
identity_providers: [
%{
base_url: "http://localhost:4002/autX/saml#foo",
id: "foo-bar-buz",
metadata: saml_metadata,
sign_metadata: false,
sign_requests: false,
signed_assertion_in_resp: false,
signed_envelopes_in_resp: false,
sp_id: "firezone"
}
]
]
# Edit
session =
session

View File

@@ -361,7 +361,7 @@ defmodule FzHttpWeb.Acceptance.AuthenticationTest do
|> visit(~p"/")
|> assert_el(Query.link("Sign in with email"))
|> click(Query.link("Sign in with email"))
|> assert_el(Query.text("Sign In"))
|> assert_el(Query.text("Sign In", minimum: 1))
|> fill_form(%{
"Email" => email,
"Password" => password

View File

@@ -11,17 +11,17 @@ defmodule FzHttpWeb.Plug.PathPrefixTest do
describe "call/2" do
test "does nothing when path prefix is not configured" do
FzHttp.Config.put_env(:path_prefix, nil)
FzHttp.Config.put_env_override(:path_prefix, nil)
conn = conn(:get, "/")
assert call(conn, []) == conn
FzHttp.Config.put_env(:path_prefix, "/")
FzHttp.Config.put_env_override(:path_prefix, "/")
conn = conn(:get, "/foo")
assert call(conn, []) == conn
end
test "removes prefix from conn.request_path" do
FzHttp.Config.put_env(:path_prefix, "/vpn/")
FzHttp.Config.put_env_override(:path_prefix, "/vpn/")
conn = conn(:get, "/vpn/foo")
assert returned_conn = call(conn, [])
assert returned_conn.request_path == "/foo"
@@ -30,7 +30,7 @@ defmodule FzHttpWeb.Plug.PathPrefixTest do
end
test "removes prefix from conn.path_info" do
FzHttp.Config.put_env(:path_prefix, "/vpn/")
FzHttp.Config.put_env_override(:path_prefix, "/vpn/")
conn = conn(:get, "/vpn/foo")
assert returned_conn = call(conn, [])
assert returned_conn.path_info == ["foo"]
@@ -39,7 +39,7 @@ defmodule FzHttpWeb.Plug.PathPrefixTest do
end
test "redirects users from not prefixed path" do
FzHttp.Config.put_env(:path_prefix, "/vpn/")
FzHttp.Config.put_env_override(:path_prefix, "/vpn/")
conn = conn(:get, "/foo")
assert returned_conn = call(conn, [])

View File

@@ -112,10 +112,6 @@ defmodule FzHttpWeb.AcceptanceCase do
{:error, e} ->
raise Wallaby.QueryError,
Query.ErrorMessage.message(query, e)
error ->
raise Wallaby.ExpectationNotMetError,
"Wallaby has encountered an internal error: #{inspect(error)} with session: #{inspect(session)}"
end
assert_has(session, query)

View File

@@ -71,7 +71,8 @@ config :wallaby,
screenshot_on_failure: true,
# XXX: Contribute to Wallaby to make this configurable on the per-process level,
# along with buffer to write logs only on process failure
js_logger: false
js_logger: false,
hackney_options: [timeout: 10_000, recv_timeout: 10_000]
config :ex_unit,
formatters: [JUnitFormatter, ExUnit.CLIFormatter],