Major milestone: Frontend implementation complete for Shattered Void MMO FRONTEND IMPLEMENTATION: - React 18 + TypeScript + Vite development environment - Tailwind CSS with custom dark theme for sci-fi aesthetic - Zustand state management with authentication persistence - Socket.io WebSocket client with auto-reconnection - Protected routing with authentication guards - Responsive design with mobile-first approach AUTHENTICATION SYSTEM: - Login/register forms with comprehensive validation - JWT token management with localStorage persistence - Password strength validation and user feedback - Protected routes and authentication guards CORE GAME INTERFACE: - Colony management dashboard with real-time updates - Resource display with live production tracking - WebSocket integration for real-time game events - Navigation with connection status indicator - Toast notifications for user feedback BACKEND ENHANCEMENTS: - Complete Research System with technology tree (23 technologies) - Fleet Management System with ship designs and movement - Enhanced Authentication with email verification and password reset - Complete game tick integration for all systems - Advanced WebSocket events for real-time updates ARCHITECTURE FEATURES: - Type-safe TypeScript throughout - Component-based architecture with reusable UI elements - API client with request/response interceptors - Error handling and loading states - Performance optimized builds with code splitting Phase 2 Status: Frontend foundation complete (Week 1-2 objectives met) Ready for: Colony management, fleet operations, research interface Next: Enhanced gameplay features and admin interface 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
479 lines
14 KiB
JavaScript
479 lines
14 KiB
JavaScript
/**
|
|
* Test Helpers
|
|
* Utility functions for setting up test data
|
|
*/
|
|
|
|
const db = require('../../database/connection');
|
|
const jwt = require('jsonwebtoken');
|
|
const bcrypt = require('bcrypt');
|
|
|
|
/**
|
|
* Create a test user with authentication token
|
|
* @param {string} email - User email
|
|
* @param {string} username - Username
|
|
* @param {string} password - Password (optional, defaults to 'testpassword')
|
|
* @returns {Promise<Object>} User and token
|
|
*/
|
|
async function createTestUser(email, username, password = 'testpassword') {
|
|
try {
|
|
// Hash password
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
|
|
// Create user
|
|
const [user] = await db('players').insert({
|
|
email,
|
|
username,
|
|
password_hash: hashedPassword,
|
|
email_verified: true,
|
|
user_group: Math.floor(Math.random() * 10),
|
|
is_active: true,
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
}).returning('*');
|
|
|
|
// Generate JWT token
|
|
const token = jwt.sign(
|
|
{ id: user.id, username: user.username },
|
|
process.env.JWT_SECRET || 'test-secret',
|
|
{ expiresIn: '24h' },
|
|
);
|
|
|
|
// Initialize player resources
|
|
const resourceTypes = await db('resource_types').where('is_active', true);
|
|
for (const resourceType of resourceTypes) {
|
|
await db('player_resources').insert({
|
|
player_id: user.id,
|
|
resource_type_id: resourceType.id,
|
|
amount: 10000, // Generous amount for testing
|
|
storage_capacity: 50000,
|
|
last_updated: new Date(),
|
|
});
|
|
}
|
|
|
|
// Initialize combat statistics
|
|
await db('combat_statistics').insert({
|
|
player_id: user.id,
|
|
battles_initiated: 0,
|
|
battles_won: 0,
|
|
battles_lost: 0,
|
|
ships_lost: 0,
|
|
ships_destroyed: 0,
|
|
total_damage_dealt: 0,
|
|
total_damage_received: 0,
|
|
total_experience_gained: 0,
|
|
resources_looted: JSON.stringify({}),
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
});
|
|
|
|
return { user, token };
|
|
} catch (error) {
|
|
console.error('Failed to create test user:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a test fleet with ships
|
|
* @param {number} playerId - Owner player ID
|
|
* @param {string} name - Fleet name
|
|
* @param {string} location - Fleet location
|
|
* @returns {Promise<Object>} Created fleet
|
|
*/
|
|
async function createTestFleet(playerId, name, location) {
|
|
try {
|
|
// Get or create a basic ship design
|
|
let shipDesign = await db('ship_designs')
|
|
.where('name', 'Test Fighter')
|
|
.where('is_public', true)
|
|
.first();
|
|
|
|
if (!shipDesign) {
|
|
[shipDesign] = await db('ship_designs').insert({
|
|
name: 'Test Fighter',
|
|
ship_class: 'fighter',
|
|
hull_type: 'light',
|
|
components: JSON.stringify({
|
|
weapons: ['basic_laser'],
|
|
shields: ['basic_shield'],
|
|
engines: ['basic_engine'],
|
|
}),
|
|
stats: JSON.stringify({
|
|
hp: 100,
|
|
attack: 15,
|
|
defense: 10,
|
|
speed: 5,
|
|
}),
|
|
cost: JSON.stringify({
|
|
scrap: 100,
|
|
energy: 50,
|
|
}),
|
|
build_time: 30,
|
|
is_public: true,
|
|
is_active: true,
|
|
hull_points: 100,
|
|
shield_points: 25,
|
|
armor_points: 10,
|
|
attack_power: 15,
|
|
attack_speed: 1.0,
|
|
movement_speed: 5,
|
|
cargo_capacity: 0,
|
|
special_abilities: JSON.stringify([]),
|
|
damage_resistances: JSON.stringify({}),
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
}).returning('*');
|
|
}
|
|
|
|
// Create fleet
|
|
const [fleet] = await db('fleets').insert({
|
|
player_id: playerId,
|
|
name,
|
|
current_location: location,
|
|
destination: null,
|
|
fleet_status: 'idle',
|
|
combat_rating: 150,
|
|
total_ship_count: 10,
|
|
fleet_composition: JSON.stringify({
|
|
'Test Fighter': 10,
|
|
}),
|
|
combat_victories: 0,
|
|
combat_defeats: 0,
|
|
movement_started: null,
|
|
arrival_time: null,
|
|
last_updated: new Date(),
|
|
created_at: new Date(),
|
|
}).returning('*');
|
|
|
|
// Add ships to fleet
|
|
await db('fleet_ships').insert({
|
|
fleet_id: fleet.id,
|
|
ship_design_id: shipDesign.id,
|
|
quantity: 10,
|
|
health_percentage: 100,
|
|
experience: 0,
|
|
created_at: new Date(),
|
|
});
|
|
|
|
// Create initial combat experience record
|
|
await db('ship_combat_experience').insert({
|
|
fleet_id: fleet.id,
|
|
ship_design_id: shipDesign.id,
|
|
battles_survived: 0,
|
|
enemies_destroyed: 0,
|
|
damage_dealt: 0,
|
|
experience_points: 0,
|
|
veterancy_level: 1,
|
|
combat_bonuses: JSON.stringify({}),
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
});
|
|
|
|
return fleet;
|
|
} catch (error) {
|
|
console.error('Failed to create test fleet:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a test colony with basic buildings
|
|
* @param {number} playerId - Owner player ID
|
|
* @param {string} name - Colony name
|
|
* @param {string} coordinates - Colony coordinates
|
|
* @param {number} planetTypeId - Planet type ID (optional)
|
|
* @returns {Promise<Object>} Created colony
|
|
*/
|
|
async function createTestColony(playerId, name, coordinates, planetTypeId = null) {
|
|
try {
|
|
// Get planet type
|
|
if (!planetTypeId) {
|
|
const planetType = await db('planet_types')
|
|
.where('is_active', true)
|
|
.first();
|
|
planetTypeId = planetType?.id || 1;
|
|
}
|
|
|
|
// Get sector
|
|
const sectorCoordinates = coordinates.split('-').slice(0, 2).join('-');
|
|
let sector = await db('galaxy_sectors')
|
|
.where('coordinates', sectorCoordinates)
|
|
.first();
|
|
|
|
if (!sector) {
|
|
[sector] = await db('galaxy_sectors').insert({
|
|
name: `Test Sector ${sectorCoordinates}`,
|
|
coordinates: sectorCoordinates,
|
|
description: 'Test sector for integration tests',
|
|
danger_level: 3,
|
|
special_rules: JSON.stringify({}),
|
|
created_at: new Date(),
|
|
}).returning('*');
|
|
}
|
|
|
|
// Create colony
|
|
const [colony] = await db('colonies').insert({
|
|
player_id: playerId,
|
|
name,
|
|
coordinates,
|
|
sector_id: sector.id,
|
|
planet_type_id: planetTypeId,
|
|
population: 1000,
|
|
max_population: 10000,
|
|
morale: 100,
|
|
loyalty: 100,
|
|
defense_rating: 50,
|
|
shield_strength: 25,
|
|
under_siege: false,
|
|
successful_defenses: 0,
|
|
times_captured: 0,
|
|
founded_at: new Date(),
|
|
last_updated: new Date(),
|
|
}).returning('*');
|
|
|
|
// Add basic buildings
|
|
const commandCenter = await db('building_types')
|
|
.where('name', 'Command Center')
|
|
.first();
|
|
|
|
if (commandCenter) {
|
|
await db('colony_buildings').insert({
|
|
colony_id: colony.id,
|
|
building_type_id: commandCenter.id,
|
|
level: 1,
|
|
health_percentage: 100,
|
|
is_under_construction: false,
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
});
|
|
}
|
|
|
|
// Add defense grid for combat testing
|
|
const defenseGrid = await db('building_types')
|
|
.where('name', 'Defense Grid')
|
|
.first();
|
|
|
|
if (defenseGrid) {
|
|
await db('colony_buildings').insert({
|
|
colony_id: colony.id,
|
|
building_type_id: defenseGrid.id,
|
|
level: 2,
|
|
health_percentage: 100,
|
|
is_under_construction: false,
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
});
|
|
}
|
|
|
|
// Initialize colony resource production
|
|
const resourceTypes = await db('resource_types').where('is_active', true);
|
|
for (const resourceType of resourceTypes) {
|
|
await db('colony_resource_production').insert({
|
|
colony_id: colony.id,
|
|
resource_type_id: resourceType.id,
|
|
production_rate: 10,
|
|
consumption_rate: 5,
|
|
current_stored: 1000,
|
|
storage_capacity: 10000,
|
|
last_calculated: new Date(),
|
|
});
|
|
}
|
|
|
|
return colony;
|
|
} catch (error) {
|
|
console.error('Failed to create test colony:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create test combat configuration
|
|
* @param {string} name - Configuration name
|
|
* @param {string} type - Combat type
|
|
* @param {Object} config - Configuration data
|
|
* @returns {Promise<Object>} Created configuration
|
|
*/
|
|
async function createTestCombatConfig(name, type, config = {}) {
|
|
try {
|
|
const [combatConfig] = await db('combat_configurations').insert({
|
|
config_name: name,
|
|
combat_type: type,
|
|
config_data: JSON.stringify({
|
|
auto_resolve: true,
|
|
preparation_time: 1,
|
|
max_rounds: 5,
|
|
round_duration: 2,
|
|
damage_variance: 0.1,
|
|
experience_gain: 1.0,
|
|
casualty_rate_min: 0.1,
|
|
casualty_rate_max: 0.7,
|
|
loot_multiplier: 1.0,
|
|
spectator_limit: 100,
|
|
priority: 100,
|
|
...config,
|
|
}),
|
|
description: `Test ${type} combat configuration`,
|
|
is_active: true,
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
}).returning('*');
|
|
|
|
return combatConfig;
|
|
} catch (error) {
|
|
console.error('Failed to create test combat config:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a completed combat encounter for testing history
|
|
* @param {number} attackerFleetId - Attacker fleet ID
|
|
* @param {number} defenderFleetId - Defender fleet ID (optional)
|
|
* @param {number} defenderColonyId - Defender colony ID (optional)
|
|
* @param {string} outcome - Combat outcome
|
|
* @returns {Promise<Object>} Created encounter
|
|
*/
|
|
async function createTestCombatEncounter(attackerFleetId, defenderFleetId = null, defenderColonyId = null, outcome = 'attacker_victory') {
|
|
try {
|
|
// Create battle
|
|
const [battle] = await db('battles').insert({
|
|
battle_type: defenderColonyId ? 'fleet_vs_colony' : 'fleet_vs_fleet',
|
|
location: 'A3-91-X',
|
|
combat_type_id: 1,
|
|
participants: JSON.stringify({
|
|
attacker_fleet_id: attackerFleetId,
|
|
defender_fleet_id: defenderFleetId,
|
|
defender_colony_id: defenderColonyId,
|
|
}),
|
|
status: 'completed',
|
|
battle_data: JSON.stringify({}),
|
|
result: JSON.stringify({ outcome }),
|
|
started_at: new Date(Date.now() - 300000), // 5 minutes ago
|
|
completed_at: new Date(),
|
|
created_at: new Date(),
|
|
}).returning('*');
|
|
|
|
// Create encounter
|
|
const [encounter] = await db('combat_encounters').insert({
|
|
battle_id: battle.id,
|
|
attacker_fleet_id: attackerFleetId,
|
|
defender_fleet_id: defenderFleetId,
|
|
defender_colony_id: defenderColonyId,
|
|
encounter_type: battle.battle_type,
|
|
location: battle.location,
|
|
initial_forces: JSON.stringify({
|
|
attacker: { ships: 10 },
|
|
defender: { ships: 8 },
|
|
}),
|
|
final_forces: JSON.stringify({
|
|
attacker: { ships: outcome === 'attacker_victory' ? 7 : 2 },
|
|
defender: { ships: outcome === 'defender_victory' ? 6 : 0 },
|
|
}),
|
|
casualties: JSON.stringify({
|
|
attacker: { ships: {}, total_ships: outcome === 'attacker_victory' ? 3 : 8 },
|
|
defender: { ships: {}, total_ships: outcome === 'defender_victory' ? 2 : 8 },
|
|
}),
|
|
combat_log: JSON.stringify([
|
|
{ round: 1, event: 'combat_start', description: 'Combat initiated' },
|
|
{ round: 1, event: 'combat_resolution', description: `${outcome.replace('_', ' ')}` },
|
|
]),
|
|
experience_gained: 100,
|
|
loot_awarded: JSON.stringify(outcome === 'attacker_victory' ? { scrap: 500, energy: 250 } : {}),
|
|
outcome,
|
|
duration_seconds: 90,
|
|
started_at: battle.started_at,
|
|
completed_at: battle.completed_at,
|
|
created_at: new Date(),
|
|
}).returning('*');
|
|
|
|
return { battle, encounter };
|
|
} catch (error) {
|
|
console.error('Failed to create test combat encounter:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for a condition to be met
|
|
* @param {Function} condition - Function that returns true when condition is met
|
|
* @param {number} timeout - Timeout in milliseconds
|
|
* @param {number} interval - Check interval in milliseconds
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async function waitForCondition(condition, timeout = 5000, interval = 100) {
|
|
const start = Date.now();
|
|
|
|
while (Date.now() - start < timeout) {
|
|
if (await condition()) {
|
|
return;
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, interval));
|
|
}
|
|
|
|
throw new Error(`Condition not met within ${timeout}ms`);
|
|
}
|
|
|
|
/**
|
|
* Clean up all test data
|
|
*/
|
|
async function cleanupTestData() {
|
|
try {
|
|
// Delete in order to respect foreign key constraints
|
|
await db('combat_logs').del();
|
|
await db('combat_encounters').del();
|
|
await db('combat_queue').del();
|
|
await db('battles').del();
|
|
await db('ship_combat_experience').del();
|
|
await db('fleet_ships').del();
|
|
await db('fleet_positions').del();
|
|
await db('fleets').del();
|
|
await db('colony_resource_production').del();
|
|
await db('colony_buildings').del();
|
|
await db('colonies').del();
|
|
await db('player_resources').del();
|
|
await db('combat_statistics').del();
|
|
await db('players').where('email', 'like', '%@test.com').del();
|
|
await db('combat_configurations').where('config_name', 'like', 'test_%').del();
|
|
await db('ship_designs').where('name', 'like', 'Test %').del();
|
|
await db('galaxy_sectors').where('name', 'like', 'Test Sector%').del();
|
|
} catch (error) {
|
|
console.error('Failed to cleanup test data:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset combat-related data between tests
|
|
*/
|
|
async function resetCombatData() {
|
|
try {
|
|
await db('combat_logs').del();
|
|
await db('combat_encounters').del();
|
|
await db('combat_queue').del();
|
|
await db('battles').del();
|
|
|
|
// Reset fleet statuses
|
|
await db('fleets').update({
|
|
fleet_status: 'idle',
|
|
last_combat: null,
|
|
});
|
|
|
|
// Reset colony siege status
|
|
await db('colonies').update({
|
|
under_siege: false,
|
|
last_attacked: null,
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to reset combat data:', error);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
createTestUser,
|
|
createTestFleet,
|
|
createTestColony,
|
|
createTestCombatConfig,
|
|
createTestCombatEncounter,
|
|
waitForCondition,
|
|
cleanupTestData,
|
|
resetCombatData,
|
|
};
|