diff --git a/CHANGELOG.md b/CHANGELOG.md index 230ef0f52..1c7356b84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Version 2.1.0-alpha.4 (2026-10-28) + +- Internal changes only. + ## Version 2.1.0-alpha.3 (2026-10-28) - Stop declaring native image generation and web search to an OpenAI provider diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b6f4284b..015fcc7af 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,6 +32,8 @@ ANKOLE_EDITION=saas mix test MIX_TEST_PARTITION=edition_build mix test integration/edition_build_test.exs --trace ``` +With `ANKOLE_EDITION=saas`, `mix test` excludes the tests tagged `:foss_sign_in`. These tests cover the Pod sign-in pages, and the SaaS edition signs in on the accounts host instead. + `edition_build_test.exs` spins up a labeled private fixture inside a temporary checkout to verify FOSS isolation, SaaS compilation and assets, private test discovery, fresh and upgraded databases, rollbacks, and a standalone release—exercising built Pod and Auth webapps over real HTTPS sessions, Principal authorization, generated API clients, and local SMTP (it tests build plumbing rather than certifying a commercial SaaS product). Before running it, install Chromium with `bunx playwright install chromium` and ensure `openssl` is on `PATH` for temporary TLS certificates. Docker builds for the control plane use Cargo to compile the native Rust Kernel and Torque libraries. To build a SaaS image from the public `Dockerfile`, pass `internals/` as a named build context: diff --git a/app/control_plane/test/ankole/e2e/pod_content_browser_test.exs b/app/control_plane/test/ankole/e2e/pod_content_browser_test.exs index b85faaa2b..8888d8092 100644 --- a/app/control_plane/test/ankole/e2e/pod_content_browser_test.exs +++ b/app/control_plane/test/ankole/e2e/pod_content_browser_test.exs @@ -5,6 +5,8 @@ defmodule Ankole.E2E.PodContentBrowserTest do alias Ankole.E2E.PodContentBrowser alias Ankole.Principals + @moduletag :foss_sign_in + test "the story browser receives a real email proof for its registered Human", ctx do assert {:ok, true} = Ankole.AppConfigure.put_global(Ankole.Setup.Config.completed_definition(), true) diff --git a/app/control_plane/test/ankole_web/controllers/oidc_controller_test.exs b/app/control_plane/test/ankole_web/controllers/oidc_controller_test.exs index b88a27287..a92afee61 100644 --- a/app/control_plane/test/ankole_web/controllers/oidc_controller_test.exs +++ b/app/control_plane/test/ankole_web/controllers/oidc_controller_test.exs @@ -1066,6 +1066,7 @@ defmodule AnkoleWeb.OIDCControllerTest do refute redirect_query(response)["code"] end + @tag :foss_sign_in test "select_account uses a held proof explicitly and separates two identities for one Client", %{conn: conn, tenant: tenant} do :ok = Ankole.DataCase.set_privileged_tenant(tenant) diff --git a/app/control_plane/test/ankole_web/controllers/upstream_oidc_login_test.exs b/app/control_plane/test/ankole_web/controllers/upstream_oidc_login_test.exs index a51e94d60..6c57c9f3b 100644 --- a/app/control_plane/test/ankole_web/controllers/upstream_oidc_login_test.exs +++ b/app/control_plane/test/ankole_web/controllers/upstream_oidc_login_test.exs @@ -75,7 +75,9 @@ defmodule AnkoleWeb.UpstreamOIDCLoginTest do auth_time = System.system_time(:second) stub_upstream(upstream["nonce"], %{"auth_time" => auth_time}) conn = callback(conn, upstream) - assert redirected_to(conn) == "/oauth/authorize/resume?oauth_request=" <> oauth_request + # The SaaS edition redirects to the absolute Pod origin. + assert %URI{path: "/oauth/authorize/resume", query: query} = URI.parse(redirected_to(conn)) + assert query == "oauth_request=" <> oauth_request assert WebSession.oauth_session(conn)["auth_time"] == auth_time assert WebSession.admin_session(conn) == nil @@ -168,7 +170,9 @@ defmodule AnkoleWeb.UpstreamOIDCLoginTest do stub_upstream(upstream["nonce"], %{}) conn = callback(conn, upstream) - assert redirected_to(conn) == "/oauth/authorize/resume?oauth_request=" <> oauth_request + # The SaaS edition redirects to the absolute Pod origin. + assert %URI{path: "/oauth/authorize/resume", query: query} = URI.parse(redirected_to(conn)) + assert query == "oauth_request=" <> oauth_request assert %{"auth_time" => nil} = WebSession.oauth_session(conn) assert WebSession.admin_session(conn) == nil end diff --git a/app/control_plane/test/ankole_web/pod_identity_selection_test.exs b/app/control_plane/test/ankole_web/pod_identity_selection_test.exs index 16413f183..aeaaf725a 100644 --- a/app/control_plane/test/ankole_web/pod_identity_selection_test.exs +++ b/app/control_plane/test/ankole_web/pod_identity_selection_test.exs @@ -88,6 +88,7 @@ defmodule AnkoleWeb.PodIdentitySelectionTest do end end + @tag :foss_sign_in test "the Pod shell exposes the Principal selected by its authentication proof", ctx do for {reference, principal} <- [{ctx.a_ref, ctx.a}, {ctx.b_ref, ctx.b}] do chooser = chooser(ctx.signed, ctx.tenant) @@ -109,6 +110,7 @@ defmodule AnkoleWeb.PodIdentitySelectionTest do end end + @tag :foss_sign_in test "the existing Pod chooser switches a proven account and keeps an open tab pinned", ctx do shell = chooser(ctx.signed, ctx.tenant) @@ -246,6 +248,7 @@ defmodule AnkoleWeb.PodIdentitySelectionTest do assert session["consoleURL"] == ctx.site_origin <> "/site-admin-console" end + @tag :foss_sign_in test "a chooser cannot use another tenant's proof or a revoked account", ctx do shell = chooser(ctx.signed, ctx.tenant) foreign = Ankole.DataCase.create_tenant() diff --git a/app/control_plane/test/test_helper.exs b/app/control_plane/test/test_helper.exs index 666f1ac83..d08ae0adb 100644 --- a/app/control_plane/test/test_helper.exs +++ b/app/control_plane/test/test_helper.exs @@ -4,5 +4,9 @@ real_llm_exclude = _ -> [real_llm: true] end -ExUnit.start(exclude: [:regenerate_golden | real_llm_exclude]) +# The SaaS edition signs in on the accounts host instead of the Pod sign-in pages. +sign_in_exclude = + if Application.get_env(:ankole, :edition) == :saas, do: [foss_sign_in: true], else: [] + +ExUnit.start(exclude: [:regenerate_golden | real_llm_exclude ++ sign_in_exclude]) Ecto.Adapters.SQL.Sandbox.mode(Ankole.Repo, :manual)