# Nathan Hinton 24 Dec 2025 # Test file for the player # spec/player_spec.rb require 'rspec' require 'json' require 'game/player' # adjust the path if needed RSpec.describe Player do let(:player_id) { SecureRandom.uuid } subject(:player) { Player.new(player_id, 'good') } # ------------------------------------------------------------------ # Helpers / setup # ------------------------------------------------------------------ before(:all) do Dir.mkdir('game_data') unless Dir.exist?('game_data') end after(:each) do # Clean up any save file that might have been created save_file = File.join('game_data', "#{player_id}.save") File.delete(save_file) if File.exist?(save_file) end # ------------------------------------------------------------------ # initialize # ------------------------------------------------------------------ describe '#initialize' do it 'sets the id and empty arrays for planets and ships' do expect(subject.player_id).to eq(player_id) expect(subject.player_planets).to be_empty expect(subject.player_ships).to be_empty expect(subject.instance_variable_get(:@home_planet)).to be_nil end it 'sets the faction correctly' do subject = Player.new(player_id, 'good') expect(subject.faction).to eq('good') subject = Player.new(player_id, 'evil') expect(subject.faction).to eq('evil') end it 'raises errors when the faction does not match' do expect{subject = Player.new(player_id, 'billy_bob_jo')}.to raise_error(PlayerError, 'Faction must be \`good\` or \`evil\`') end end # ------------------------------------------------------------------ # set_resources # ------------------------------------------------------------------ describe '#set_resources' do let(:credits) { 1000 } let(:metal) { 500 } let(:crystals) { 200 } let(:fuel) { 300 } it 'stores the resources hash correctly' do subject.set_resources(credits, metal, crystals, fuel) expect(subject.player_resources).to eq( 'credits' => credits, 'metal' => metal, 'crystals' => crystals, 'fuel' => fuel ) end end # ------------------------------------------------------------------ # connect / connected? # ------------------------------------------------------------------ describe '#connect and #connected?' do let(:socket) { instance_double('TCPSocket', closed?: false, puts: nil) } it 'stores the socket' do subject.connect(socket) expect(subject.instance_variable_get(:@socket)).to eq(socket) end context 'when the socket is nil' do it 'returns false' do subject.instance_variable_set(:@socket, nil) expect(subject.connected?).to be false end end context 'when the socket is closed' do before do allow(socket).to receive(:closed?).and_return(true) subject.connect(socket) end it 'returns false' do expect(subject.connected?).to be false end end context 'when the socket is open' do before { subject.connect(socket) } it 'returns true' do expect(subject.connected?).to be true end end end # ------------------------------------------------------------------ # claim_planet # ------------------------------------------------------------------ describe '#claim_planet' do let(:planet) { double('planet', claimable?: true, owner: nil) } context 'when the planet is claimable' do it 'adds the planet to the player and calls claim on the planet' do expect(planet).to receive(:claim).with(subject) subject.claim_planet(planet) expect(subject.player_planets).to include(planet) end end context 'when the planet is not claimable' do before { allow(planet).to receive(:claimable?).and_return(false) } it 'does nothing' do expect(planet).not_to receive(:claim) subject.claim_planet(planet) expect(subject.player_planets).not_to include(planet) end end end # ------------------------------------------------------------------ # make_home_planet # ------------------------------------------------------------------ describe '#make_home_planet' do context 'when the player owns the planet' do let(:planet) { double('planet', owner: subject) } it 'sets the home planet' do subject.make_home_planet(planet) expect(subject.instance_variable_get(:@home_planet)).to eq(planet) end end context 'when the player does NOT own the planet' do let(:planet) { double('planet', owner: nil) } it 'returns an error hash' do result = subject.make_home_planet(planet) expect(result).to eq( 'type' => 'error', 'payload' => 'Planet must be owned by the player' ) end end end # ------------------------------------------------------------------ # update # ------------------------------------------------------------------ describe '#update' do let(:credits) { 1000 } let(:metal) { 500 } let(:crystals) { 200 } let(:fuel) { 300 } let(:request_id) { 42 } before do subject.set_resources(credits, metal, crystals, fuel) subject.player_planets << double('planet', info_update: 'test_data') subject.player_ships << double('ship') end it 'returns a hash in the expected format' do expected = { 'type' => 'update', 'domain' => 'player', 'request_id' => request_id, 'payload' => { 'resources' => subject.player_resources, 'planets' => ['test_data'], 'ships' => subject.player_ships } } expect(subject.update(request_id)).to eq(expected) end end # ------------------------------------------------------------------ # send_update # ------------------------------------------------------------------ describe '#send_update' do let(:socket) { instance_double('TCPSocket', closed?: false, puts: nil) } before { subject.connect(socket) } context 'when connected' do it 'sends the update JSON' do expect(socket).to receive(:puts).with(kind_of(String)) subject.send_update end end context 'when not connected' do before { subject.instance_variable_set(:@socket, nil) } it 'does nothing' do expect { subject.send_update }.not_to raise_error end end context 'when socket.puts raises an exception' do before do allow(socket).to receive(:puts).and_raise(StandardError) allow(socket).to receive(:close) end it 'closes the socket' do expect(socket).to receive(:close) subject.send_update end end end # ------------------------------------------------------------------ # save_to_file / load_from_save # ------------------------------------------------------------------ describe 'file persistence' do let(:credits) { 1000 } let(:metal) { 500 } let(:crystals) { 200 } let(:fuel) { 300 } before do subject.set_resources(credits, metal, crystals, fuel) end it 'writes a marshaled file and can read it back' do file_id = subject.save_to_file expect(file_id).to eq(player_id) loaded_player = Player.load_from_save(player_id) expect(loaded_player).to be_a(Player) expect(loaded_player.player_id).to eq(player_id) expect(loaded_player.player_resources).to eq( 'credits' => credits, 'metal' => metal, 'crystals' => crystals, 'fuel' => fuel, ) end end # ------------------------------------------------------------------ # marshal_dump / marshal_load # ------------------------------------------------------------------ describe '#marshal_dump and #marshal_load' do let(:credits) { 1000 } let(:metal) { 500 } let(:crystals) { 200 } let(:fuel) { 300 } before do subject.set_resources(credits, metal, crystals, fuel) end it 'returns a hash with the expected keys' do dump = subject.marshal_dump expect(dump).to include( player_id: subject.player_id, player_resources: subject.player_resources ) end it 'restores the player via marshal_load' do dump = subject.marshal_dump # Create a brand‑new instance and load the dumped data new_player = Player.new('dummy-id', 'good') new_player.marshal_load(dump) expect(new_player.player_id).to eq(subject.player_id) expect(new_player.player_resources).to eq(subject.player_resources) end end end