import 'dart:async'; import 'package:test/test.dart'; import 'package:oto/oto/agent/agent_config.dart'; import 'package:oto/oto/agent/agent_runner.dart'; import 'package:oto/oto/agent/edge_registration_client.dart'; class FakeEdgeAgentSession implements EdgeAgentSession { @override final RegistrationResult result; bool closed = false; FakeEdgeAgentSession(this.result); @override Future close() async { closed = true; } } class FakeEdgeRegistrationClient extends EdgeRegistrationClient { final RegistrationResult Function(AgentConfig config) _onRegister; FakeEdgeAgentSession? lastSession; FakeEdgeRegistrationClient(this._onRegister); @override Future openSession(AgentConfig config) async { final session = FakeEdgeAgentSession(_onRegister(config)); lastSession = session; return session; } } void main() { const validBootstrapYaml = ''' agent: id: "agent-123" alias: "my-agent" enrollment_token: "token-456" edge: url: "127.0.0.1:8080" runtime: install_dir: "/usr/bin" workspace_root: "/var/oto" log_dir: "/var/log/oto" '''; late AgentConfig validConfig; setUp(() { validConfig = AgentConfig.fromYamlContent(validBootstrapYaml); }); group('EdgeEndpoint', () { test('EdgeEndpoint parses host and port from config edge url', () { final endpoint1 = EdgeEndpoint.parse('127.0.0.1:8080'); expect(endpoint1.host, '127.0.0.1'); expect(endpoint1.port, 8080); final endpoint2 = EdgeEndpoint.parse('http://edge-server:9000'); expect(endpoint2.host, 'edge-server'); expect(endpoint2.port, 9000); final endpoint3 = EdgeEndpoint.parse('https://secure-edge'); expect(endpoint3.host, 'secure-edge'); expect(endpoint3.port, 443); final endpoint4 = EdgeEndpoint.parse('edge-no-port'); expect(endpoint4.host, 'edge-no-port'); expect(endpoint4.port, 80); }); }); group('EdgeRegistrationClient.register one-shot', () { test('register returns session result and closes the session', () async { final fakeClient = FakeEdgeRegistrationClient((config) { return RegistrationResult.accepted( 'node-123', 'alias-123', {'concurrency': 1}); }); final result = await fakeClient.register(validConfig); expect(result.accepted, isTrue); expect(result.nodeId, 'node-123'); expect(fakeClient.lastSession, isNotNull); expect(fakeClient.lastSession!.closed, isTrue); }); }); group('AgentRunner', () { test('AgentRunner maps config token to register request', () async { AgentConfig? capturedConfig; final fakeClient = FakeEdgeRegistrationClient((config) { capturedConfig = config; return RegistrationResult.accepted( 'node-123', 'alias-123', {'concurrency': 1}); }); final runner = DefaultAgentRunner( client: fakeClient, onLog: (msg) {}, waitForShutdown: () async {}, ); await runner.run(validConfig); expect(capturedConfig, isNotNull); expect(capturedConfig!.agent.enrollmentToken, 'token-456'); }); test('AgentRunner keeps session open until shutdown then closes', () async { final fakeClient = FakeEdgeRegistrationClient((config) { return RegistrationResult.accepted( 'node-123', 'alias-123', {'concurrency': 1}); }); final shutdown = Completer(); final runner = DefaultAgentRunner( client: fakeClient, onLog: (msg) {}, waitForShutdown: () => shutdown.future, ); final runFuture = runner.run(validConfig); // 종료 신호 전에는 session이 살아 있어야 한다. await Future.delayed(Duration.zero); expect(fakeClient.lastSession, isNotNull); expect(fakeClient.lastSession!.closed, isFalse); shutdown.complete(); await runFuture; expect(fakeClient.lastSession!.closed, isTrue); }); test('AgentRunner reports rejected registration and closes session', () async { final fakeClient = FakeEdgeRegistrationClient((config) { return RegistrationResult.rejected('Invalid token'); }); final runner = DefaultAgentRunner( client: fakeClient, onLog: (msg) {}, waitForShutdown: () async {}, ); await expectLater( () => runner.run(validConfig), throwsA(isA() .having((e) => e.message, 'message', 'Invalid token')), ); expect(fakeClient.lastSession, isNotNull); expect(fakeClient.lastSession!.closed, isTrue); }); }); }