Module: LcpRuby::Authentication::TestSupport

Defined in:
lib/lcp_ruby/authentication/test_support.rb

Overview

Public test-support helper for Phase 2 bearer authentication.

Lets host-app specs sign valid (and invalid) JWTs that LCP’s bearer pipeline accepts, without spinning up a real IdP or stubbing engine internals. Loaded only under ‘Rails.env.test?` — `install_provider!` raises `NotInTestEnv` everywhere else as defense-in-depth against accidentally shipping the helper to production.

Stubs are installed via rspec-mocks’s ‘allow().to receive()`, so they auto-reset between examples — no manual teardown needed beyond `TestSupport.reset!` for the per-process keypair / provider cache.

Usage:

require "lcp_ruby/authentication/test_support"

RSpec.configure do |config|
  config.before do
    @oidc_signer = LcpRuby::Authentication::TestSupport.install_provider!(
      "entra",
      issuer:   "https://test-issuer.example/",
      audience: "api://lcp-test"
    )
  end

  config.after { LcpRuby::Authentication::TestSupport.reset! }
end

it "lists deals via OIDC bearer" do
  token = @oidc_signer.sign(sub: "user-1", roles: ["LCP.Admin"])
  get "/api/deals", headers: { "Authorization" => "Bearer #{token}" }
  expect(response).to have_http_status(:ok)
end

Negative-case helpers on the Signer:

signer.sign_expired(sub: "user-1")        # exp in the past
signer.sign_wrong_audience(sub: "user-1") # aud mismatch
signer.sign_unknown_kid(sub: "user-1")    # kid not in JWKS
signer.sign_tampered(sub: "user-1")       # signature mutilated

Defined Under Namespace

Classes: NotInTestEnv, Signer, Stubber

Constant Summary collapse

DEFAULT_KID =
"test-kid"

Class Method Summary collapse

Class Method Details

.install_provider!(name, issuer:, audience:, kid: DEFAULT_KID) ⇒ Object

Installs a synthetic OIDC provider into LCP’s caches and returns a Signer for producing JWTs that the bearer pipeline will accept. Idempotent across multiple calls within the same example: each call adds its provider to the stubbed ‘oidc_providers` list. Must be called from inside an RSpec example or `before` hook (rspec-mocks is required).



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/lcp_ruby/authentication/test_support.rb', line 60

def install_provider!(name, issuer:, audience:, kid: DEFAULT_KID)
  ensure_test_env!
  ensure_rspec_mocks!
  name = name.to_s

  rsa_key    = (keypairs[name] ||= OpenSSL::PKey::RSA.generate(2048))
  public_jwk = JSON::JWK.new(rsa_key.public_key).merge(kid: kid, use: "sig", alg: "RS256")
  provider   = build_provider(name: name, audience: audience)

  providers[name] = provider
  per_provider[name] = { provider: provider, issuer: issuer, kid: kid, public_jwk: public_jwk }

  install_stubs!
  register_oidc_resolver!

  Signer.new(provider: provider, issuer: issuer, audience: audience, kid: kid, rsa_key: rsa_key)
end

.reset!Object

Clears the per-process provider/keypair cache and the JwksCache. rspec-mocks auto-resets between examples, so you typically only need this if you ‘install_provider!`-loop within a single example.



81
82
83
84
85
86
# File 'lib/lcp_ruby/authentication/test_support.rb', line 81

def reset!
  @providers    = nil
  @per_provider = nil
  @keypairs     = nil
  JwksCache.clear! if defined?(JwksCache)
end