require 'rails_helper' RSpec.describe RsAuthorizationCode, type: :model do let(:user) { create :user } # Example value from RFC 7636, Appendix B let(:challenge) { "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" } def new_code(**attrs) RsAuthorizationCode.new( user: user, client_id: "example.com", redirect_uri: "https://example.com", permissions: %w(documents:rw), code_challenge: challenge, **attrs ) end def build_code(**attrs) new_code(**attrs).tap(&:save!) end describe "validations" do it "is valid with a well-formed S256 challenge" do expect(new_code).to be_valid end it "rejects a challenge shorter than 43 characters" do expect(new_code(code_challenge: "short")).not_to be_valid end it "rejects a challenge with invalid characters" do expect(new_code(code_challenge: "#{'a' * 42}!")).not_to be_valid end it "rejects an unsupported challenge method" do code = new_code(code_challenge_method: "plain") expect(code).not_to be_valid expect(code.errors[:code_challenge_method]).to be_present end end describe "#create" do it "generates a raw code and stores only its digest" do code = build_code expect(code.code).to be_present expect(code.code_digest).to eq(Digest::SHA256.hexdigest(code.code)) end it "sets a short expiry" do code = build_code expect(code.expires_at).to be_within(5.seconds).of(60.seconds.from_now) end end describe ".find_by_code" do it "finds a code by its raw value" do code = build_code expect(RsAuthorizationCode.find_by_code(code.code)).to eq(code) end it "returns nil for an unknown code" do expect(RsAuthorizationCode.find_by_code("does-not-exist")).to be_nil end it "returns nil for a blank code" do expect(RsAuthorizationCode.find_by_code(nil)).to be_nil end it "returns nil for an expired code" do code = build_code(expires_at: 1.minute.ago) expect(RsAuthorizationCode.find_by_code(code.code)).to be_nil end end end