Add actor parameter to all tests requiring authorization
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit adds actor: system_actor to all Ash operations in tests that require authorization.
This commit is contained in:
parent
4c846f8bba
commit
a6cdeaa18d
75 changed files with 4649 additions and 2865 deletions
|
|
@ -8,8 +8,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
use MvWeb.ConnCase, async: true
|
||||
require Ash.Query
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
describe "E2E: New OIDC user registration" do
|
||||
test "new user can register via OIDC", %{conn: _conn} do
|
||||
test "new user can register via OIDC", %{conn: _conn, actor: actor} do
|
||||
# Simulate OIDC callback for brand new user
|
||||
user_info = %{
|
||||
"sub" => "new_oidc_user_123",
|
||||
|
|
@ -18,10 +23,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Call register action
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert {:ok, new_user} = result
|
||||
assert to_string(new_user.email) == "newuser@example.com"
|
||||
|
|
@ -30,17 +38,20 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Verify user can be found by oidc_id
|
||||
{:ok, [found_user]} =
|
||||
Mv.Accounts.read_sign_in_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.read_sign_in_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert found_user.id == new_user.id
|
||||
end
|
||||
end
|
||||
|
||||
describe "E2E: Existing OIDC user sign-in" do
|
||||
test "existing OIDC user can sign in and email updates", %{conn: _conn} do
|
||||
test "existing OIDC user can sign in and email updates", %{conn: _conn, actor: actor} do
|
||||
# Create OIDC user
|
||||
user =
|
||||
create_test_user(%{
|
||||
|
|
@ -56,10 +67,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Register (upsert) with new email
|
||||
{:ok, updated_user} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: updated_user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: updated_user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Same user, updated email
|
||||
assert updated_user.id == user.id
|
||||
|
|
@ -70,7 +84,7 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
describe "E2E: OIDC with existing password account (Email Collision)" do
|
||||
test "OIDC registration with password account email triggers PasswordVerificationRequired",
|
||||
%{conn: _conn} do
|
||||
%{conn: _conn, actor: actor} do
|
||||
# Step 1: Create a password-only user
|
||||
password_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -86,10 +100,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Step 3: Should fail with PasswordVerificationRequired
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = result
|
||||
|
|
@ -106,7 +123,7 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
end
|
||||
|
||||
test "full E2E flow: OIDC collision -> password verification -> account linked",
|
||||
%{conn: _conn} do
|
||||
%{conn: _conn, actor: actor} do
|
||||
# Step 1: Create password user
|
||||
password_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -122,10 +139,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
}
|
||||
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Extract the error
|
||||
password_error =
|
||||
|
|
@ -142,12 +162,12 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
{:ok, linked_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Query.filter(id == ^password_user.id)
|
||||
|> Ash.read_one!()
|
||||
|> Ash.read_one!(actor: actor)
|
||||
|> Ash.Changeset.for_update(:link_oidc_id, %{
|
||||
oidc_id: user_info["sub"],
|
||||
oidc_user_info: user_info
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
# Verify account is now linked
|
||||
assert linked_user.id == password_user.id
|
||||
|
|
@ -158,17 +178,20 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Step 5: User can now sign in via OIDC
|
||||
{:ok, [signed_in_user]} =
|
||||
Mv.Accounts.read_sign_in_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.read_sign_in_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert signed_in_user.id == password_user.id
|
||||
assert signed_in_user.oidc_id == "oidc_link_888"
|
||||
end
|
||||
|
||||
test "E2E: OIDC collision with different email at provider updates email after linking",
|
||||
%{conn: _conn} do
|
||||
%{conn: _conn, actor: actor} do
|
||||
# Password user with old email
|
||||
password_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -199,12 +222,12 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
{:ok, linked_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Query.filter(id == ^password_user.id)
|
||||
|> Ash.read_one!()
|
||||
|> Ash.read_one!(actor: actor)
|
||||
|> Ash.Changeset.for_update(:link_oidc_id, %{
|
||||
oidc_id: updated_user_info["sub"],
|
||||
oidc_user_info: updated_user_info
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
# Email should be updated to match OIDC provider
|
||||
assert to_string(linked_user.email) == "new@example.com"
|
||||
|
|
@ -213,7 +236,10 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
end
|
||||
|
||||
describe "E2E: OIDC with linked member" do
|
||||
test "E2E: email sync to member when linking OIDC to password account", %{conn: _conn} do
|
||||
test "E2E: email sync to member when linking OIDC to password account", %{
|
||||
conn: _conn,
|
||||
actor: actor
|
||||
} do
|
||||
# Create member
|
||||
member =
|
||||
Ash.Seed.seed!(Mv.Membership.Member, %{
|
||||
|
|
@ -239,10 +265,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Collision detected
|
||||
{:error, %Ash.Error.Invalid{}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# After password verification, link OIDC with NEW email
|
||||
updated_user_info = %{
|
||||
|
|
@ -253,24 +282,27 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
{:ok, linked_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Query.filter(id == ^password_user.id)
|
||||
|> Ash.read_one!()
|
||||
|> Ash.read_one!(actor: actor)
|
||||
|> Ash.Changeset.for_update(:link_oidc_id, %{
|
||||
oidc_id: updated_user_info["sub"],
|
||||
oidc_user_info: updated_user_info
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
# User email updated
|
||||
assert to_string(linked_user.email) == "newmember@example.com"
|
||||
|
||||
# Member email should be synced
|
||||
{:ok, updated_member} = Ash.get(Mv.Membership.Member, member.id)
|
||||
{:ok, updated_member} = Ash.get(Mv.Membership.Member, member.id, actor: actor)
|
||||
assert to_string(updated_member.email) == "newmember@example.com"
|
||||
end
|
||||
end
|
||||
|
||||
describe "E2E: Security scenarios" do
|
||||
test "E2E: password-only user cannot be accessed via OIDC without password", %{conn: _conn} do
|
||||
test "E2E: password-only user cannot be accessed via OIDC without password", %{
|
||||
conn: _conn,
|
||||
actor: actor
|
||||
} do
|
||||
# Create password user
|
||||
_password_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -287,10 +319,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Sign-in should fail (no matching oidc_id)
|
||||
result =
|
||||
Mv.Accounts.read_sign_in_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.read_sign_in_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
case result do
|
||||
{:ok, []} ->
|
||||
|
|
@ -305,17 +340,23 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Registration should trigger password requirement
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert Enum.any?(errors, fn err ->
|
||||
match?(%Mv.Accounts.User.Errors.PasswordVerificationRequired{}, err)
|
||||
end)
|
||||
end
|
||||
|
||||
test "E2E: user with oidc_id cannot be hijacked by different OIDC provider", %{conn: _conn} do
|
||||
test "E2E: user with oidc_id cannot be hijacked by different OIDC provider", %{
|
||||
conn: _conn,
|
||||
actor: actor
|
||||
} do
|
||||
# User linked to OIDC provider A
|
||||
_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -331,10 +372,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
|
||||
# Should trigger hard error (not PasswordVerificationRequired)
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should have hard error about "already linked to a different OIDC account"
|
||||
assert Enum.any?(errors, fn
|
||||
|
|
@ -351,7 +395,10 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "E2E: empty string oidc_id is treated as password-only account", %{conn: _conn} do
|
||||
test "E2E: empty string oidc_id is treated as password-only account", %{
|
||||
conn: _conn,
|
||||
actor: actor
|
||||
} do
|
||||
# User with empty oidc_id
|
||||
_password_user =
|
||||
create_test_user(%{
|
||||
|
|
@ -367,10 +414,13 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
}
|
||||
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should require password (empty string = no OIDC)
|
||||
assert Enum.any?(errors, fn err ->
|
||||
|
|
@ -380,32 +430,38 @@ defmodule MvWeb.OidcE2EFlowTest do
|
|||
end
|
||||
|
||||
describe "E2E: Error scenarios" do
|
||||
test "E2E: OIDC registration without oidc_id fails", %{conn: _conn} do
|
||||
test "E2E: OIDC registration without oidc_id fails", %{conn: _conn, actor: actor} do
|
||||
user_info = %{
|
||||
"preferred_username" => "noid@example.com"
|
||||
}
|
||||
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert Enum.any?(errors, fn err ->
|
||||
match?(%Ash.Error.Changes.InvalidChanges{}, err)
|
||||
end)
|
||||
end
|
||||
|
||||
test "E2E: OIDC registration without email fails", %{conn: _conn} do
|
||||
test "E2E: OIDC registration without email fails", %{conn: _conn, actor: actor} do
|
||||
user_info = %{
|
||||
"sub" => "noemail_123"
|
||||
}
|
||||
|
||||
{:error, %Ash.Error.Invalid{errors: errors}} =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
})
|
||||
Mv.Accounts.create_register_with_rauthy(
|
||||
%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{}
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert Enum.any?(errors, fn err ->
|
||||
match?(%Ash.Error.Changes.Required{field: :email}, err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue