Files
invoiceninja-nixos/tests/nixos-test.nix
T
awkawbandClaude Sonnet 5 7d7d717745 nixos-module: work around upstream language-list caching bug
Migration 2025_08_31_224356_additional_languages_af_ca_id.php runs
during `migrate`, before db:seed's LanguageSeeder has populated the
other 42 rows of the languages table, and does
Cache::forever('languages', Language::all()) - permanently caching
just the 3 languages (Afrikaans, Catalan, Indonesian) it sees at that
moment. StaticServiceProvider checks this cache key before ever
querying the DB again, so every fresh install's language dropdown
(e.g. the "Welcome to Invoice Ninja" company setup wizard) was stuck
showing only those 3 entries, in their own native-language spelling
(ctrans() translates each name using the active locale).

Adds an unconditional `invoiceninja-manage cache:forget languages` to
invoiceninja-data-setup.service, and a regression test asserting
/api/v1/statics returns more than 40 languages. Verified directly:
before the fix, a fresh account's statics call returned exactly 3
languages; after, it returns all 45.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-26 15:17:22 -04:00

91 lines
4.4 KiB
Nix

{ nixpkgsPath, system }:
let
# nixosTest forbids setting `nixpkgs.config` on a node once `pkgs` is externally supplied,
# so allowUnfree (required to build pkgs.chromium) has to be baked into this pkgs instance
# up front rather than set from within common.nix like the interactive VM does.
pkgs = import nixpkgsPath {
inherit system;
config.allowUnfree = true;
};
adminEmail = "admin@email.com";
adminPassword = pkgs.lib.strings.trim (builtins.readFile ./invoice_ninja_test_password);
in
pkgs.testers.nixosTest {
name = "invoiceninja";
nodes = {
nginx.imports = [ (import ./common.nix { proxyServer = "nginx"; }) ];
caddy.imports = [ (import ./common.nix { proxyServer = "caddy"; }) ];
};
testScript = ''
import json
def check_invoiceninja(machine, proxy_unit):
machine.wait_for_unit("mysql.service")
machine.wait_for_unit("redis-invoiceninja.service")
machine.wait_for_unit("phpfpm-invoiceninja.service")
machine.wait_for_unit("invoiceninja-queue-worker.service")
machine.wait_for_unit(proxy_unit)
machine.wait_for_open_port(80)
# phpfpm-invoiceninja/invoiceninja-queue-worker both Require= invoiceninja-data-setup,
# so systemd already waited for it to finish above. Confirm the one-time setup steps
# it guards actually completed, rather than just that the unit exited.
with subtest("one-time setup (migrate, seed, admin account) completed"):
machine.succeed("test -f /var/lib/invoiceninja/.initial-migration")
machine.succeed("test -f /var/lib/invoiceninja/.db-seeded")
machine.succeed("test -f /var/lib/invoiceninja/.admin-created")
with subtest("application shell is served over HTTP"):
response = machine.succeed("curl -sf http://localhost/")
assert "Invoice Ninja" in response, f"unexpected response from /: {response}"
with subtest("web UI login page is served"):
# The UI is a client-side-routed React SPA, so curl (no JS) only sees the same
# index.html shell as "/" - this confirms the proxy/Laravel resolve the /login route
# to that shell instead of 404ing, which is what a browser needs before it can
# render the actual login form.
login_page = machine.succeed("curl -sf http://localhost/login")
assert "Invoice Ninja" in login_page, f"unexpected response from /login: {login_page}"
with subtest("invoiceninja-manage wrapper can run artisan commands"):
machine.succeed("invoiceninja-manage migrate:status")
with subtest("scheduler timer is enabled and schedule:run succeeds"):
# Without this, the app's dashboard reports "cron jobs are not enabled".
machine.succeed("systemctl is-active invoiceninja-scheduler.timer")
machine.succeed("systemctl start --wait invoiceninja-scheduler.service")
with subtest("admin account can log in via the API"):
login_response = machine.succeed(
"curl -sf -X POST http://localhost/api/v1/login "
+ "-H 'Content-Type: application/json' "
+ "-d '{\"email\":\"${adminEmail}\",\"password\":\"${adminPassword}\"}'"
)
assert "${adminEmail}" in login_response, f"login did not succeed: {login_response}"
with subtest("full language list is available, not just the af/ca/id migration's 3"):
# Regression check: migration 2025_08_31_224356_additional_languages_af_ca_id.php runs
# during `migrate`, before db:seed's LanguageSeeder has populated the other 42 rows, and
# does Cache::forever('languages', Language::all()) - permanently caching just the 3
# languages it sees at that moment. invoiceninja-data-setup works around this with
# `invoiceninja-manage cache:forget languages`; this confirms the workaround is in place.
token = json.loads(login_response)["data"][0]["token"]["token"]
statics = machine.succeed(
f"curl -sf http://localhost/api/v1/statics -H 'X-API-TOKEN: {token}'"
)
language_count = len(json.loads(statics)["languages"])
assert language_count > 40, f"expected ~45 languages, got {language_count}"
nginx.start()
caddy.start()
check_invoiceninja(nginx, "nginx.service")
check_invoiceninja(caddy, "caddy.service")
'';
}