import 'dart:convert'; import 'package:cyberhybridhub_server/alpaca/alpaca_assets_client.dart'; import 'package:cyberhybridhub_server/alpaca/alpaca_env.dart'; import 'package:cyberhybridhub_server/alpaca/alpaca_models.dart'; import 'package:http/http.dart' as http; import 'package:test/test.dart'; import '../helpers/fixture_loader.dart'; import '../helpers/mock_http_client.dart'; void main() { late FixtureLoader fixtures; late AlpacaEnv env; setUp(() { fixtures = FixtureLoader(); env = AlpacaEnv.fromMap({ 'ALPACA_API_KEY_ID': 'test-key', 'ALPACA_API_SECRET_KEY': 'test-secret', 'ALPACA_TRADING_BASE_URL': 'https://paper-api.alpaca.markets', 'ALPACA_DATA_BASE_URL': 'https://data.alpaca.markets', }); }); test('listActiveTradable issues GET with auth headers and query', () async { final String body = await fixtures.loadString('alpaca_assets_active.json'); final MockHttpClient mock = MockHttpClient() ..whenGet( '/v2/assets', http.Response(body, 200, headers: { 'content-type': 'application/json', }), ); final AlpacaAssetsClient client = AlpacaAssetsClient(env: env, httpClient: mock); await client.listActiveTradable(); expect(mock.requests, hasLength(1)); final http.BaseRequest req = mock.requests.single; expect(req.method, 'GET'); expect( req.url.toString(), startsWith('https://paper-api.alpaca.markets/v2/assets'), ); expect(req.url.queryParameters['status'], 'active'); expect(req.url.queryParameters['asset_class'], 'us_equity'); expect(req.headers['APCA-API-KEY-ID'], 'test-key'); expect(req.headers['APCA-API-SECRET-KEY'], 'test-secret'); }); test('listActiveTradable parses the fixture into AlpacaAsset rows', () async { final String body = await fixtures.loadString('alpaca_assets_active.json'); final MockHttpClient mock = MockHttpClient() ..whenGet( '/v2/assets', http.Response(body, 200, headers: { 'content-type': 'application/json', }), ); final AlpacaAssetsClient client = AlpacaAssetsClient(env: env, httpClient: mock); final List assets = await client.listActiveTradable(); expect(assets, hasLength(5)); final AlpacaAsset aapl = assets.firstWhere((AlpacaAsset a) => a.symbol == 'AAPL'); expect(aapl.assetClass, 'us_equity'); expect(aapl.exchange, 'NASDAQ'); expect(aapl.name, 'Apple Inc. Common Stock'); expect(aapl.status, 'active'); expect(aapl.tradable, isTrue); expect(aapl.fractionable, isTrue); final AlpacaAsset pinkSheet = assets.firstWhere((AlpacaAsset a) => a.symbol == 'PNKZZ'); expect(pinkSheet.tradable, isFalse); expect(pinkSheet.fractionable, isFalse); final AlpacaAsset brk = assets.firstWhere((AlpacaAsset a) => a.symbol == 'BRK.B'); expect(brk.tradable, isTrue); expect(brk.fractionable, isFalse); }); test('401 unauthorized throws AlpacaAssetsException with code + body', () async { final MockHttpClient mock = MockHttpClient() ..whenGet( '/v2/assets', http.Response( jsonEncode({'message': 'forbidden.'}), 401, headers: {'content-type': 'application/json'}, ), ); final AlpacaAssetsClient client = AlpacaAssetsClient(env: env, httpClient: mock); await expectLater( client.listActiveTradable(), throwsA( isA() .having((AlpacaAssetsException e) => e.message, 'message', contains('401')) .having((AlpacaAssetsException e) => e.message, 'body', contains('forbidden')), ), ); }); test('500 server error throws AlpacaAssetsException with code + body', () async { final MockHttpClient mock = MockHttpClient() ..whenGet( '/v2/assets', http.Response('upstream exploded', 500), ); final AlpacaAssetsClient client = AlpacaAssetsClient(env: env, httpClient: mock); await expectLater( client.listActiveTradable(), throwsA( isA() .having((AlpacaAssetsException e) => e.message, 'message', contains('500')) .having((AlpacaAssetsException e) => e.message, 'body', contains('upstream exploded')), ), ); }); test('empty array response returns [] and does not throw', () async { final MockHttpClient mock = MockHttpClient() ..whenGet( '/v2/assets', http.Response('[]', 200, headers: { 'content-type': 'application/json', }), ); final AlpacaAssetsClient client = AlpacaAssetsClient(env: env, httpClient: mock); final List assets = await client.listActiveTradable(); expect(assets, isEmpty); }); }