Back to Repositories

Validating OpenID Connect User Authentication in Postal

This test suite validates OpenID Connect (OIDC) functionality in the User model, focusing on user authentication and identity management. The tests cover OIDC user identification, matching, and profile updates within the Postal server application.

Test Coverage Overview

The test suite provides comprehensive coverage of OIDC user authentication scenarios:

  • OIDC status verification through UID presence
  • User lookup and matching based on OIDC credentials
  • Profile synchronization with OIDC provider data
  • Existing user migration to OIDC authentication

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with factory-based test data generation. The implementation leverages context blocks for different user scenarios and mock objects for OIDC configuration.

  • Factory-based user creation
  • Mocked OIDC configuration
  • Structured context separation
  • Logger verification for operations

Technical Details

Testing infrastructure includes:

  • RSpec as the testing framework
  • FactoryBot for test data generation
  • TestLogger for logging verification
  • Rails test helpers for database operations
  • Mocked Postal::Config.oidc configuration

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test contexts for different scenarios
  • Comprehensive edge case coverage
  • Clear test case organization
  • Proper setup and teardown management
  • Effective use of before blocks for test preparation
  • Verification of both state changes and logging behavior

postalserver/postal

spec/models/user/oidc_spec.rb

            
# frozen_string_literal: true

require "rails_helper"

RSpec.describe User do
  let(:user) { build(:user) }

  describe "#oidc?" do
    it "returns true if the user has an OIDC UID" do
      user.oidc_uid = "123"
      expect(user.oidc?).to be true
    end

    it "returns false if the user does not have an OIDC UID" do
      user.oidc_uid = nil
      expect(user.oidc?).to be false
    end
  end

  describe ".find_from_oidc" do
    let(:issuer) { "https://identity.example.com" }

    before do
      allow(Postal::Config.oidc).to receive(:enabled?).and_return(true)
      allow(Postal::Config.oidc).to receive(:issuer).and_return(issuer)
      allow(Postal::Config.oidc).to receive(:email_address_field).and_return("email")
    end

    let(:uid) { "abcdef" }
    let(:oidc_name) { "John Smith" }
    let(:oidc_email) { "[email protected]" }

    let(:auth) { { "sub" => uid, "email" => oidc_email, "name" => oidc_name } }
    let(:logger) { TestLogger.new }

    subject(:result) { described_class.find_from_oidc(auth, logger: logger) }

    context "when there is a user that matchers the UID and issuer" do
      before do
        @existing_user = create(:user, oidc_uid: uid, oidc_issuer: issuer, first_name: "mary",
                                       last_name: "apples", email_address: "[email protected]")
      end

      it "returns that user" do
        expect(result).to eq @existing_user
      end

      it "updates the name and email address" do
        result
        @existing_user.reload
        expect(@existing_user.first_name).to eq "John"
        expect(@existing_user.last_name).to eq "Smith"
        expect(@existing_user.email_address).to eq "[email protected]"
      end

      it "logs" do
        result
        expect(logger).to have_logged(/found user with UID abcdef/i)
      end
    end

    context "when there is no user which matches the UID and issuer" do
      context "when there is a user which matches the email address without an OIDC UID" do
        before do
          @existing_user = create(:user, first_name: "mary",
                                         last_name: "apples", email_address: "[email protected]")
        end

        it "returns that user" do
          expect(result).to eq @existing_user
        end

        it "adds the UID and issuer to the user" do
          result
          @existing_user.reload
          expect(@existing_user.oidc_uid).to eq uid
          expect(@existing_user.oidc_issuer).to eq issuer
        end

        it "updates the name if changed" do
          result
          @existing_user.reload
          expect(@existing_user.first_name).to eq "John"
          expect(@existing_user.last_name).to eq "Smith"
        end

        it "removes the password" do
          @existing_user.password = "password"
          @existing_user.save!
          result
          @existing_user.reload
          expect(@existing_user.password_digest).to be_nil
        end

        it "logs" do
          result
          expect(logger).to have_logged(/no user with UID abcdef/)
          expect(logger).to have_logged(/found user with e-mail address [email protected]/)
        end
      end

      context "when there is no user which matches the email address" do
        it "returns nil" do
          expect(result).to be_nil
        end

        it "logs" do
          result
          expect(logger).to have_logged(/no user with UID abcdef/)
          expect(logger).to have_logged(/no user with e-mail address/)
        end
      end
    end
  end
end