286 lines
8.6 KiB
Ruby
286 lines
8.6 KiB
Ruby
# 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
|