defmodule MvWeb.OidcIntegrationTest do use MvWeb.ConnCase, async: true # Test OIDC callback scenarios by directly calling the actions # This simulates what happens during real OIDC authentication describe "OIDC sign-in scenarios" do test "existing OIDC user with unchanged email can sign in" do # Create user with OIDC ID user = create_test_user(%{ email: "existing@example.com", oidc_id: "existing_oidc_123" }) # Simulate OIDC callback data user_info = %{ "sub" => "existing_oidc_123", "preferred_username" => "existing@example.com" } # Test sign_in_with_rauthy action directly {:ok, [found_user]} = Mv.Accounts.read_sign_in_with_rauthy(%{ user_info: user_info, oauth_tokens: %{} }) assert found_user.id == user.id assert to_string(found_user.email) == "existing@example.com" assert found_user.oidc_id == "existing_oidc_123" end test "new OIDC user gets created via register_with_rauthy" do # Simulate OIDC callback for completely new user user_info = %{ "sub" => "brand_new_oidc_456", "preferred_username" => "newuser@example.com" } # Test register_with_rauthy action case Mv.Accounts.create_register_with_rauthy(%{ user_info: user_info, oauth_tokens: %{} }) do {:ok, new_user} -> assert to_string(new_user.email) == "newuser@example.com" assert new_user.oidc_id == "brand_new_oidc_456" assert is_nil(new_user.hashed_password) {:error, error} -> flunk("Should have created new user: #{inspect(error)}") end end end describe "OIDC error and edge case scenarios" do test "OIDC registration with conflicting email and OIDC ID shows error" do # Create user with email and OIDC ID _existing_user = create_test_user(%{ email: "conflict@example.com", oidc_id: "oidc_conflict_1" }) # Try to register with same email but different OIDC ID user_info = %{ "sub" => "oidc_conflict_2", "preferred_username" => "conflict@example.com" } result = Mv.Accounts.create_register_with_rauthy(%{ user_info: user_info, oauth_tokens: %{} }) # Should fail due to unique constraint assert {:error, %Ash.Error.Invalid{errors: errors}} = result assert Enum.any?(errors, fn %Ash.Error.Changes.InvalidAttribute{field: :email, message: message} -> String.contains?(message, "has already been taken") _ -> false end) end test "OIDC registration with missing sub and id should fail" do user_info = %{ "preferred_username" => "nosub@example.com" } result = Mv.Accounts.create_register_with_rauthy(%{ user_info: user_info, oauth_tokens: %{} }) assert {:error, %Ash.Error.Invalid{ errors: [%Ash.Error.Changes.InvalidChanges{vars: [user_info: msg]}] }} = result assert String.contains?(msg, "OIDC user_info must contain a non-empty 'sub' or 'id' field") end test "OIDC registration with missing preferred_username should fail" do user_info = %{ "sub" => "noemail_oidc_123" } result = Mv.Accounts.create_register_with_rauthy(%{ user_info: user_info, oauth_tokens: %{} }) assert {:error, %Ash.Error.Invalid{errors: errors}} = result assert Enum.any?(errors, fn err -> match?(%Ash.Error.Changes.Required{field: :email}, err) end) end test "OIDC registration with existing OIDC ID and different email updates email" do existing_user = create_test_user(%{ email: "old@example.com", oidc_id: "oidc_update_email" }) user_info = %{ "sub" => "oidc_update_email", "preferred_username" => "new@example.com" } {:ok, user} = Mv.Accounts.create_register_with_rauthy(%{ user_info: user_info, oauth_tokens: %{} }) assert user.id == existing_user.id assert to_string(user.email) == "new@example.com" assert user.oidc_id == "oidc_update_email" end test "OIDC registration with alternative OIDC ID field (id instead of sub)" do user_info = %{ "id" => "alt_oidc_id_123", "preferred_username" => "altid@example.com" } {:ok, user} = Mv.Accounts.create_register_with_rauthy(%{ user_info: user_info, oauth_tokens: %{} }) assert user.oidc_id == "alt_oidc_id_123" assert to_string(user.email) == "altid@example.com" end end end