require "rails_helper" RSpec.describe User, type: :model do let(:valid_attributes) { { nickname: "player_one", password: "secret123" } } describe "validations" do it "is valid with a nickname and password" do expect(User.new(valid_attributes)).to be_valid end it "requires a nickname" do user = User.new(valid_attributes.merge(nickname: nil)) expect(user).not_to be_valid expect(user.errors[:nickname]).to be_present end it "requires a password" do user = User.new(nickname: "player_one", password: nil) expect(user).not_to be_valid expect(user.errors[:password]).to be_present end it "rejects nicknames shorter than 3 characters" do expect(User.new(valid_attributes.merge(nickname: "ab"))).not_to be_valid end it "rejects nicknames longer than 24 characters" do expect(User.new(valid_attributes.merge(nickname: "a" * 25))).not_to be_valid end it "rejects passwords shorter than 6 characters" do expect(User.new(valid_attributes.merge(password: "12345"))).not_to be_valid end it "requires nicknames to be unique case-insensitively" do User.create!(valid_attributes) duplicate = User.new(valid_attributes.merge(nickname: "PLAYER_ONE")) expect(duplicate).not_to be_valid expect(duplicate.errors[:nickname]).to be_present end it "accepts valid image formats for avatar" do user = User.new(valid_attributes) user.avatar.attach( io: StringIO.new("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15c4"), filename: "avatar.png", content_type: "image/png" ) expect(user).to be_valid end it "rejects unsupported mime types for avatar" do user = User.new(valid_attributes) user.avatar.attach( io: StringIO.new("plain text"), filename: "test.txt", content_type: "text/plain" ) expect(user).not_to be_valid expect(user.errors[:avatar]).to include("must be a PNG, JPEG, GIF or WebP image") end end describe "rating" do it "defaults to the neutral initial value of 100" do expect(User.create!(valid_attributes).rating).to eq(100) end it "requires a rating" do user = User.new(valid_attributes) user.rating = nil expect(user).not_to be_valid expect(user.errors[:rating]).to be_present end end describe "nickname normalization" do it "strips surrounding whitespace" do user = User.create!(valid_attributes.merge(nickname: " player_one ")) expect(user.nickname).to eq("player_one") end end describe "password authentication" do it "authenticates with the correct password" do user = User.create!(valid_attributes) expect(user.authenticate("secret123")).to eq(user) end it "rejects a wrong password" do user = User.create!(valid_attributes) expect(user.authenticate("wrong")).to be_falsey end it "does not require a password when updating other attributes" do user = User.create!(valid_attributes) expect(user.update(nickname: "new_nickname")).to be true end end describe "#finished_matches" do let(:user) { User.create!(valid_attributes) } let(:opponent) { User.create!(nickname: "opponent", password: "secret123") } let(:outsider) { User.create!(nickname: "outsider", password: "secret123") } def build_match(attrs) Match.create!({ player1: user, player2: opponent, status: :ongoing, game_server: "ws://game-server:8080/ws", started_at: Time.current, player1_rating_before: 100, player2_rating_before: 100, player1_rating_change: 0, player2_rating_change: 0 }.merge(attrs)) end it "returns only finished matches of the user ordered by finished_at desc" do older = build_match(status: :player1_won, finished_at: 2.days.ago) newer = build_match(player1: opponent, player2: user, status: :player2_won, finished_at: 1.day.ago) build_match(status: :ongoing, finished_at: nil) build_match(player1: opponent, player2: outsider, status: :player1_won, finished_at: Time.current) expect(user.finished_matches.to_a).to eq([ newer, older ]) end end end