cyberhybridhub/server/test/alpaca/alpaca_assets_client_test.dart
2026-05-31 11:17:12 -05:00

155 lines
4.9 KiB
Dart

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(<String, String>{
'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: <String, String>{
'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: <String, String>{
'content-type': 'application/json',
}),
);
final AlpacaAssetsClient client =
AlpacaAssetsClient(env: env, httpClient: mock);
final List<AlpacaAsset> 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(<String, dynamic>{'message': 'forbidden.'}),
401,
headers: <String, String>{'content-type': 'application/json'},
),
);
final AlpacaAssetsClient client =
AlpacaAssetsClient(env: env, httpClient: mock);
await expectLater(
client.listActiveTradable(),
throwsA(
isA<AlpacaAssetsException>()
.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<AlpacaAssetsException>()
.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: <String, String>{
'content-type': 'application/json',
}),
);
final AlpacaAssetsClient client =
AlpacaAssetsClient(env: env, httpClient: mock);
final List<AlpacaAsset> assets = await client.listActiveTradable();
expect(assets, isEmpty);
});
}