require "rails_helper" RSpec.describe WarpEngine::ApplicationToken, type: :model do before do allow(WarpEngine.config).to receive(:application_token_owner_class).and_return("TestOwner") end describe "token generation" do it "generates a plain token on create and stores only its digest and prefix" do token = create(:application_token) expect(token.plain_token).to match(/\A\h{48}\z/) expect(token.token_prefix).to eq(token.plain_token.first(8)) expect(token.token_digest).to eq(Digest::SHA256.hexdigest(token.plain_token)) end it "does not expose the plain token on a reloaded record" do token = create(:application_token) expect(described_class.find(token.id).plain_token).to be_nil end end describe "validations" do it "requires an owner" do token = build(:application_token, owner: nil) expect(token).not_to be_valid expect(token.errors[:owner]).to be_present end it "requires a name" do expect(build(:application_token, name: nil)).not_to be_valid end it "requires at least one scope" do expect(build(:application_token, scopes: [])).not_to be_valid end it "fills owner_type from the configuration" do token = create(:application_token) expect(token.owner_type).to eq("TestOwner") end it "rejects an owner_type differing from the configuration" do token = build(:application_token, owner_type: "WarpEngine::Software") expect(token).not_to be_valid expect(token.errors[:owner_type]).to be_present end it "rejects creation when no owner class is configured" do allow(WarpEngine.config).to receive(:application_token_owner_class).and_return(nil) token = build(:application_token, owner_type: "TestOwner") expect(token).not_to be_valid expect(token.errors[:base]).to be_present end end describe "#scopes_string" do it "round-trips a comma separated list" do token = build(:application_token) token.scopes_string = "update, deploy,update ," expect(token.scopes).to eq(%w[update deploy]) expect(token.scopes_string).to eq("update, deploy") end end describe ".authenticate" do it "returns the token for a valid plain token and scope" do token = create(:application_token) expect(described_class.authenticate(token.plain_token, required_scope: "update")).to eq(token) end it "returns nil for a blank or unknown token" do create(:application_token) expect(described_class.authenticate(nil)).to be_nil expect(described_class.authenticate("")).to be_nil expect(described_class.authenticate("nem-letezo")).to be_nil end it "returns nil when the required scope is missing" do token = create(:application_token, scopes: [ "deploy" ]) expect(described_class.authenticate(token.plain_token, required_scope: "update")).to be_nil end it "returns nil for an expired token" do token = create(:application_token, :expired) expect(described_class.authenticate(token.plain_token, required_scope: "update")).to be_nil end it "returns nil for a revoked token" do token = create(:application_token) token.revoke! expect(described_class.authenticate(token.plain_token, required_scope: "update")).to be_nil end end describe "#revoke!" do it "soft deletes the token" do token = create(:application_token) token.revoke! expect(described_class.find_by(id: token.id)).to be_nil expect(described_class.unscoped.find(token.id).deleted_at).to be_present end end describe "#touch_last_used!" do it "stamps last_used_at" do token = create(:application_token) expect { token.touch_last_used! }.to change { token.reload.last_used_at }.from(nil) end end end