155 lines
4.9 KiB
Dart
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);
|
|
});
|
|
}
|