module runner import freeflowuniverse.herolib.core.redisclient import freeflowuniverse.herolib.core.playbook {PlayBook} import freeflowuniverse.herolib.baobab.engine { Engine, Context } __global ( entries shared map[string]string ) // Mock actor implementation for testing struct TestActor implements Actor { pub: name string = 'test_actor' pub mut: redis_conn redisclient.Redis } // Implement the Actor interface pub fn (mut actor TestActor) process_job(j job.Job) ! { mut redis_conn := actor.redis_conn()! // Update job status to started job.update_status(mut redis_conn, j.id, .started) or { return error('Failed to update job status to started: ${err}') } // Run the job using the engine result := actor.engine.run_in_context(j.script, db_path: actor.db_path caller_id: j.caller_id context_id: j.context_id ) or { // Handle execution error job.update_status(mut redis_conn, j.id, .error)! job.set_error(mut redis_conn, j.id, '${err}')! return err } // Update job status to finished and set result job.update_status(mut redis_conn, j.id, .finished) or { return error('Failed to update job status to finished: ${err}') } job.set_result(mut redis_conn, j.id, result) or { return error('Failed to set job result: ${err}') } } fn test_actor_interface_defaults() { actor := TestActor{ name: 'test_actor' } // Test default values from interface assert actor.name == 'test_actor' } fn test_actor_queue_key() { actor := TestActor{ name: 'test_actor' } assert actor.queue_key()! == 'runner:test_actor' } // Mock player function for testing fn mock_player(mut plbook PlayBook) ! { // Simple test player that adds some content action := plbook.get(filter:'entry.define')! entries['entry'] = action.params.get!('entry')! } fn test_actor_run_job() { mut e := Engine{ players: [] } // Register a simple test player e.register_player(mock_player) or { panic('Failed to register player: ${err}') } actor := TestActor{ id: 'test_runner' db_path: '/tmp/test_run.db' engine: e } // Create a test job test_job := job.new( caller_id: 'test_caller', context_id: 'test_context', script: 'test script', script_type: .v ) // Run the job result := actor.run_job(test_job) or { panic('Failed to run job: ${err}') } assert result.len > 0 } fn test_actor_run_job_with_context() { mut engine := Engine{ players: [] } // Register a player that uses context engine.register_player(fn (mut plbook playbook.PlayBook) ! { // This player might access context variables plbook.add_result('Context-aware execution') }) or { panic('Failed to register context player: ${err}') } actor := TestActor{ id: 'context_actor' db_path: '/tmp/context_test.db' engine: engine } // Create a job with specific context test_job := job.Job{ id: 'context_job_1' caller_id: 'context_caller' context_id: 'context_123' script: 'context_script' script_type: .osis status: .dispatched // ... other fields with defaults } result := actor.run_job(test_job) or { panic('Failed to run context job: ${err}') } assert result.len > 0 } fn test_actor_process_job_success() { mut e := Engine{ players: [] } // Register a successful player e.register_player(fn (mut plbook playbook.PlayBook) ! { plbook.add_result('Success!') }) or { panic('Failed to register success player: ${err}') } actor := TestActor{ id: 'success_actor' engine: e } // Create test job test_job := job.new_job('success_caller', 'success_context', 'success script', .v) // Process the job actor.process_job(test_job) or { panic('Failed to process job: ${err}') } // Process the job actor.process_job(test_job, mut mock_redis) or { panic('Failed to process job: ${err}') } // Verify Redis operations were called assert mock_redis.operations.len > 0 // Check that status was updated to started and then finished job_key := 'hero:job:${test_job.id}' assert mock_redis.job_status[job_key] == 'finished' assert mock_redis.job_results[job_key].len > 0 } fn test_actor_process_job_error() { mut engine := Engine{ players: [] } // Register a failing player engine.register_player(fn (mut plbook playbook.PlayBook) ! { return error('Test error') }) or { panic('Failed to register failing player: ${err}') } actor := TestActor{ id: 'error_actor' engine: engine } // Create test job test_job := job.new_job('error_caller', 'error_context', 'error script', .v) // Mock Redis connection mut mock_redis := MockRedisConn{ operations: [] job_status: map[string]string{} job_results: map[string]string{} job_errors: map[string]string{} } // Process the job (should handle error gracefully) if result := actor.process_job(test_job, mut mock_redis) { panic('Expected job processing to fail') } // Verify error was recorded job_key := 'hero:job:${test_job.id}' assert mock_redis.job_status[job_key] == 'error' assert mock_redis.job_errors[job_key].len > 0 } fn test_multiple_actors() { mut engine1 := Engine{ players: [] } mut engine2 := Engine{ players: [] } engine1.register_player(fn (mut plbook playbook.PlayBook) ! { plbook.add_result('Actor 1 result') }) or { panic('Failed to register player 1: ${err}') } engine2.register_player(fn (mut plbook playbook.PlayBook) ! { plbook.add_result('Actor 2 result') }) or { panic('Failed to register player 2: ${err}') } actor1 := TestActor{ id: 'actor_1' engine: engine1 } actor2 := TestActor{ id: 'actor_2' engine: engine2 } // Test that actors have different queue keys queue1 := actor1.queue_key() or { panic('Failed to get queue key 1: ${err}') } queue2 := actor2.queue_key() or { panic('Failed to get queue key 2: ${err}') } assert queue1 != queue2 assert queue1.contains('actor_1') assert queue2.contains('actor_2') // Test that actors can run jobs independently job1 := job.new_job('caller1', 'context1', 'script1', .v) job2 := job.new_job('caller2', 'context2', 'script2', .osis) result1 := actor1.run_job(job1) or { panic('Failed to run job 1: ${err}') } result2 := actor2.run_job(job2) or { panic('Failed to run job 2: ${err}') } assert result1.len > 0 assert result2.len > 0 }