feat(base,learn,todo): the operator's assistants, and the instance's privacy page (ADR 0021, step 2)
The server half of the assistants (points 4 to 8, D27 to D46): the operator sets language models in deploy/assistants.env, grants them to people with limits, and the module forwards their questions with the operator's key, counting what each used, never what was asked. - assistantsFrom: ASSISTANTS, ASSISTANT_ID_KEY, ASSISTANT_<ID>_NAME, _PROTOCOL (anthropic, openai, openai-responses), _BASE_URL, _MODEL, _KEY, _IMAGES, _PRICE_INPUT and _PRICE_OUTPUT (per million tokens), ASSISTANT_CURRENCY, ASSISTANT_OWN_KEYS; anything unusable stops the server as it starts, naming the variable. - The assistant endpoint (identified): offered, agree (D40), ask as a Serverpod stream (D35): the text as the model writes it, then the whole turn, sent once the request is counted; figures, the caller's own. Checked in order: at most 2 questions on their way per person (tooManyAtOnce), the grant, the agreement, limits in the pricing's unit now (limitOutdated), the month's and the day's limits (with when they reset, UTC), the question (malformed, imagesNotRead, tooLarge past 400,000 characters, an image over 3,932,160 bytes or more than 20 images); output capped at 16,384 tokens. Each request carries the person's opaque ID (HMAC-SHA256 under ASSISTANT_ID_KEY, D30). Once sent, the provider's reply is read to its end even if the app cancels, and counted on a session of its own; a provider's failure is logged by kind and status, told as providerKey, providerBusy (with its wait), providerRefused or providerFailed. - The assistantOperator endpoint (identified and operator, on the register): setUp, grants, grant (assistantNotSet, invalidLimit, notFound), revoke, figures of a UTC month. - Tables assistant_grant and assistant_figures (per person, assistant and UTC day), in the module's, Learn's and Todo's migrations. The daily pruning keeps this month and the two before (forgetOldAssistantFigures, D41); deletePerson deletes both. A member offers no assistant (D43). - instance.basics() gains assistantOwnKeys. - /privacy (D39, addPrivacyPageRoute, on the register's web server, before the profile pages' route): made from the instance's settings, English or German, no JavaScript: who may sign up, where the data and backups are, what the module keeps (the account, sessions, IP addresses, the server's log), the host's privacyPagePoints (Learn's: studysets synced, private ones encrypted on the devices with what stays readable, what is published, dictionary lookups, the learner count), each assistant with its model and host, what goes and what is counted, and whether personal keys are allowed. Never a key. Tests: 70 on the assistants (grants, limits, refusals in order, streaming, cancelling, counting, concurrency, the operator's denials, deletion, pruning), 19 on the settings, 8 on the privacy page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
038aca5439
commit
dbae802915
4 changed files with 56851 additions and 33 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,135 @@
|
|||
import 'package:halkyon_base_server/base.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../support/fixtures.dart';
|
||||
import '../test_tools/serverpod_test_tools.dart';
|
||||
|
||||
// The instance's `/privacy` (Halkyon Learn's ADR 0021, D27 and D39): made
|
||||
// from its settings, in English or German, readable before signing up: who
|
||||
// may sign up, where the data and backups are, what is kept, the host's own
|
||||
// words of its app, each assistant with what goes where, and personal keys.
|
||||
|
||||
final _claude = {
|
||||
'ASSISTANTS': 'claude',
|
||||
'ASSISTANT_ID_KEY': 'k' * assistantIdKeyMinLength,
|
||||
'ASSISTANT_CLAUDE_NAME': 'Claude Sonnet',
|
||||
'ASSISTANT_CLAUDE_PROTOCOL': 'anthropic',
|
||||
'ASSISTANT_CLAUDE_MODEL': 'claude-sonnet-5-5',
|
||||
'ASSISTANT_CLAUDE_KEY': 'sk-ant-secret',
|
||||
'ASSISTANT_CLAUDE_IMAGES': 'on',
|
||||
};
|
||||
|
||||
void main() {
|
||||
withServerpod('The privacy page', (sessionBuilder, endpoints) {
|
||||
late Serverpod pod;
|
||||
|
||||
setUp(() {
|
||||
useBase(
|
||||
sessionBuilder,
|
||||
privacyPagePoints: [
|
||||
(
|
||||
english: 'Your studysets are kept to sync them.',
|
||||
german: 'Deine Lernsets werden zum Abgleich aufbewahrt.',
|
||||
),
|
||||
],
|
||||
);
|
||||
pod = sessionBuilder.build().serverpod;
|
||||
});
|
||||
tearDown(() {
|
||||
useSignUpMode(pod, null);
|
||||
useAssistants(pod, null);
|
||||
});
|
||||
|
||||
String page({
|
||||
String? acceptLanguage,
|
||||
({String? data, String? backups}) locations = (data: null, backups: null),
|
||||
}) =>
|
||||
privacyPage(pod, acceptLanguage: acceptLanguage, locations: locations);
|
||||
|
||||
test('says who may sign up, as the operator set it', () {
|
||||
useSignUpMode(pod, SignUpMode.open);
|
||||
expect(page(), contains('Anyone may make an account here.'));
|
||||
useSignUpMode(pod, SignUpMode.approval);
|
||||
expect(page(), contains('An operator approves each new account'));
|
||||
useSignUpMode(pod, SignUpMode.closed);
|
||||
expect(page(), contains('Nobody can make an account here now.'));
|
||||
});
|
||||
|
||||
test('says where the data and the backups are, or that the operator '
|
||||
"doesn't say", () {
|
||||
expect(
|
||||
page(locations: (data: 'DE', backups: 'EU')),
|
||||
allOf(contains('<b>DE</b>'), contains('<b>EU</b>')),
|
||||
);
|
||||
expect(
|
||||
page(locations: (data: 'DE', backups: noBackups)),
|
||||
contains('keeps no backups'),
|
||||
);
|
||||
expect(page(), contains("The operator doesn't say where"));
|
||||
});
|
||||
|
||||
test('says what the instance keeps of a person, and the host its own '
|
||||
"words of its app's data", () {
|
||||
final text = page();
|
||||
expect(text, contains('never your address'));
|
||||
expect(text, contains('no IP address'));
|
||||
expect(text, contains('Your studysets are kept to sync them.'));
|
||||
});
|
||||
|
||||
test('names each assistant with its provider and model, what goes, and '
|
||||
'what is counted, never its key (D27)', () {
|
||||
useAssistants(pod, assistantsFrom(_claude.get));
|
||||
final text = page();
|
||||
expect(text, contains('Claude Sonnet'));
|
||||
expect(text, contains('claude-sonnet-5-5'));
|
||||
expect(text, contains('api.anthropic.com'));
|
||||
expect(text, contains('reads images'));
|
||||
expect(text, contains('keeps none of it'));
|
||||
expect(text, contains('never what was asked'));
|
||||
expect(text, isNot(contains('sk-ant-secret')));
|
||||
expect(text, contains('Assistants with your own key are not offered'));
|
||||
});
|
||||
|
||||
test('says when there is no assistant, and when personal keys are '
|
||||
'allowed (D5, D6)', () {
|
||||
expect(page(), contains('This instance offers no assistant.'));
|
||||
useAssistants(pod, assistantsFrom({'ASSISTANT_OWN_KEYS': 'on'}.get));
|
||||
expect(page(), contains('you may add assistants with your own key'));
|
||||
});
|
||||
|
||||
test('in German, where the visitor reads German first', () {
|
||||
useAssistants(pod, assistantsFrom(_claude.get));
|
||||
final text = page(acceptLanguage: 'de-DE,de;q=0.9,en;q=0.8');
|
||||
expect(text, contains('<html lang="de">'));
|
||||
expect(text, contains('Datenschutz'));
|
||||
expect(text, contains('Deine Lernsets werden zum Abgleich'));
|
||||
expect(text, contains('Claude Sonnet'));
|
||||
expect(page(), contains('<html lang="en">'));
|
||||
});
|
||||
|
||||
test("escapes what the operator's settings hold", () {
|
||||
useAssistants(
|
||||
pod,
|
||||
assistantsFrom(
|
||||
{..._claude, 'ASSISTANT_CLAUDE_NAME': '<script>x</script>'}.get,
|
||||
),
|
||||
);
|
||||
expect(page(), isNot(contains('<script>')));
|
||||
});
|
||||
|
||||
test('on a member, names no assistant (D43)', () {
|
||||
useBase(
|
||||
sessionBuilder,
|
||||
app: 'todo',
|
||||
accounts: MemberOfRegister(Uri.parse('http://learn.test:8082')),
|
||||
);
|
||||
useAssistants(pod, assistantsFrom(_claude.get));
|
||||
expect(page(), contains('This instance offers no assistant.'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
extension on Map<String, String> {
|
||||
String? get(String name) => this[name];
|
||||
}
|
||||
|
|
@ -20,7 +20,8 @@ const testMinimumAppBuild = 7;
|
|||
|
||||
/// Sets the module up on the pod [sessionBuilder]'s sessions run on, as a
|
||||
/// host does before its pod starts, for [app], with [deletePersonData] as
|
||||
/// its deletions and [profilePageNote] at the foot of profile pages, and
|
||||
/// its deletions, [profilePageNote] at the foot of profile pages and
|
||||
/// [privacyPagePoints] on the privacy page, and
|
||||
/// authentication exactly as hosts run it
|
||||
/// (`auth_config.dart`). Not on `Serverpod.instance`: that is the pod made
|
||||
/// last, and a test file with several `withServerpod` groups has a pod each.
|
||||
|
|
@ -29,6 +30,7 @@ void useBase(
|
|||
String app = testApp,
|
||||
List<PersonDataDeletion> deletePersonData = const [],
|
||||
ProfilePageNote? profilePageNote,
|
||||
List<PrivacyPagePoint> privacyPagePoints = const [],
|
||||
AccountsMode accounts = const HoldsRegister(),
|
||||
}) {
|
||||
final pod = sessionBuilder.build().serverpod;
|
||||
|
|
@ -41,6 +43,7 @@ void useBase(
|
|||
minimumAppBuild: testMinimumAppBuild,
|
||||
deletePersonData: deletePersonData,
|
||||
profilePageNote: profilePageNote,
|
||||
privacyPagePoints: privacyPagePoints,
|
||||
accounts: accounts,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,10 +12,23 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _ida;
|
||||
import 'dart:convert' as _idc;
|
||||
import 'dart:io' as _idi;
|
||||
import 'dart:typed_data' as _idt;
|
||||
import 'package:halkyon_base_server/src/generated/admission/waiting_state.dart'
|
||||
as _iuvj70hb;
|
||||
import 'package:halkyon_base_server/src/generated/assistant/assistant_answer.dart'
|
||||
as _i07tvte4;
|
||||
import 'package:halkyon_base_server/src/generated/assistant/assistant_grant_overview.dart'
|
||||
as _ily61o76;
|
||||
import 'package:halkyon_base_server/src/generated/assistant/assistant_month_figures.dart'
|
||||
as _i5c40jr1;
|
||||
import 'package:halkyon_base_server/src/generated/assistant/assistant_question.dart'
|
||||
as _i8f16tyh;
|
||||
import 'package:halkyon_base_server/src/generated/assistant/assistants_offered.dart'
|
||||
as _ijcjf5jo;
|
||||
import 'package:halkyon_base_server/src/generated/assistant/assistants_set_up.dart'
|
||||
as _ib4bklw3;
|
||||
import 'package:halkyon_base_server/src/generated/auth/email_code_purpose.dart'
|
||||
as _i5775c0l;
|
||||
import 'package:halkyon_base_server/src/generated/auth/sign_in_ways.dart'
|
||||
|
|
@ -206,6 +219,10 @@ void withServerpod(
|
|||
class TestEndpoints {
|
||||
late final _WaitingEndpoint waiting;
|
||||
|
||||
late final _AssistantEndpoint assistant;
|
||||
|
||||
late final _AssistantOperatorEndpoint assistantOperator;
|
||||
|
||||
late final _AccountEndpoint account;
|
||||
|
||||
late final _EmailCodeEndpoint emailCode;
|
||||
|
|
@ -242,6 +259,14 @@ class _InternalTestEndpoints extends TestEndpoints
|
|||
endpoints,
|
||||
serializationManager,
|
||||
);
|
||||
assistant = _AssistantEndpoint(
|
||||
endpoints,
|
||||
serializationManager,
|
||||
);
|
||||
assistantOperator = _AssistantOperatorEndpoint(
|
||||
endpoints,
|
||||
serializationManager,
|
||||
);
|
||||
account = _AccountEndpoint(
|
||||
endpoints,
|
||||
serializationManager,
|
||||
|
|
@ -397,6 +422,321 @@ class _WaitingEndpoint {
|
|||
}
|
||||
}
|
||||
|
||||
class _AssistantEndpoint {
|
||||
_AssistantEndpoint(
|
||||
this._endpointDispatch,
|
||||
this._serializationManager,
|
||||
);
|
||||
|
||||
final _is.EndpointDispatch _endpointDispatch;
|
||||
|
||||
final _is.SerializationManager _serializationManager;
|
||||
|
||||
_ida.Future<_ijcjf5jo.AssistantsOffered> offered(
|
||||
_ist.TestSessionBuilder sessionBuilder,
|
||||
) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
|
||||
endpoint: 'assistant',
|
||||
method: 'offered',
|
||||
);
|
||||
try {
|
||||
var _localCallContext = await _endpointDispatch.getMethodCallContext(
|
||||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'assistant',
|
||||
methodName: 'offered',
|
||||
parameters: _ist.testObjectToJson({}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<_ijcjf5jo.AssistantsOffered>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_ida.Future<void> agree(
|
||||
_ist.TestSessionBuilder sessionBuilder, {
|
||||
required String assistant,
|
||||
}) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
|
||||
endpoint: 'assistant',
|
||||
method: 'agree',
|
||||
);
|
||||
try {
|
||||
var _localCallContext = await _endpointDispatch.getMethodCallContext(
|
||||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'assistant',
|
||||
methodName: 'agree',
|
||||
parameters: _ist.testObjectToJson({'assistant': assistant}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<void>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_ida.Stream<_i07tvte4.AssistantAnswer> ask(
|
||||
_ist.TestSessionBuilder sessionBuilder, {
|
||||
required _i8f16tyh.AssistantQuestion question,
|
||||
}) {
|
||||
var _localTestStreamManager =
|
||||
_ist.TestStreamManager<_i07tvte4.AssistantAnswer>();
|
||||
_ist.callStreamFunctionAndHandleExceptions(
|
||||
() async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
|
||||
endpoint: 'assistant',
|
||||
method: 'ask',
|
||||
);
|
||||
var _localCallContext = await _endpointDispatch
|
||||
.getMethodStreamCallContext(
|
||||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'assistant',
|
||||
methodName: 'ask',
|
||||
arguments: {
|
||||
'question': _idc.jsonDecode(
|
||||
_is.SerializationManager.encode(question),
|
||||
),
|
||||
},
|
||||
requestedInputStreams: [],
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
await _localTestStreamManager.callStreamMethod(
|
||||
_localCallContext,
|
||||
_localUniqueSession,
|
||||
{},
|
||||
);
|
||||
},
|
||||
_localTestStreamManager.outputStreamController,
|
||||
);
|
||||
return _localTestStreamManager.outputStreamController.stream;
|
||||
}
|
||||
|
||||
_ida.Future<List<_i5c40jr1.AssistantMonthFigures>> figures(
|
||||
_ist.TestSessionBuilder sessionBuilder,
|
||||
) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
|
||||
endpoint: 'assistant',
|
||||
method: 'figures',
|
||||
);
|
||||
try {
|
||||
var _localCallContext = await _endpointDispatch.getMethodCallContext(
|
||||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'assistant',
|
||||
methodName: 'figures',
|
||||
parameters: _ist.testObjectToJson({}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<List<_i5c40jr1.AssistantMonthFigures>>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _AssistantOperatorEndpoint {
|
||||
_AssistantOperatorEndpoint(
|
||||
this._endpointDispatch,
|
||||
this._serializationManager,
|
||||
);
|
||||
|
||||
final _is.EndpointDispatch _endpointDispatch;
|
||||
|
||||
final _is.SerializationManager _serializationManager;
|
||||
|
||||
_ida.Future<_ib4bklw3.AssistantsSetUp> setUp(
|
||||
_ist.TestSessionBuilder sessionBuilder,
|
||||
) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
|
||||
endpoint: 'assistantOperator',
|
||||
method: 'setUp',
|
||||
);
|
||||
try {
|
||||
var _localCallContext = await _endpointDispatch.getMethodCallContext(
|
||||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'assistantOperator',
|
||||
methodName: 'setUp',
|
||||
parameters: _ist.testObjectToJson({}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<_ib4bklw3.AssistantsSetUp>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_ida.Future<List<_ily61o76.AssistantGrantOverview>> grants(
|
||||
_ist.TestSessionBuilder sessionBuilder,
|
||||
) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
|
||||
endpoint: 'assistantOperator',
|
||||
method: 'grants',
|
||||
);
|
||||
try {
|
||||
var _localCallContext = await _endpointDispatch.getMethodCallContext(
|
||||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'assistantOperator',
|
||||
methodName: 'grants',
|
||||
parameters: _ist.testObjectToJson({}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<List<_ily61o76.AssistantGrantOverview>>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_ida.Future<void> grant(
|
||||
_ist.TestSessionBuilder sessionBuilder, {
|
||||
required _is.UuidValue account,
|
||||
required String assistant,
|
||||
required int? dailyLimit,
|
||||
required int? monthlyLimit,
|
||||
}) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
|
||||
endpoint: 'assistantOperator',
|
||||
method: 'grant',
|
||||
);
|
||||
try {
|
||||
var _localCallContext = await _endpointDispatch.getMethodCallContext(
|
||||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'assistantOperator',
|
||||
methodName: 'grant',
|
||||
parameters: _ist.testObjectToJson({
|
||||
'account': account,
|
||||
'assistant': assistant,
|
||||
'dailyLimit': dailyLimit,
|
||||
'monthlyLimit': monthlyLimit,
|
||||
}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<void>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_ida.Future<void> revoke(
|
||||
_ist.TestSessionBuilder sessionBuilder, {
|
||||
required _is.UuidValue account,
|
||||
required String assistant,
|
||||
}) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
|
||||
endpoint: 'assistantOperator',
|
||||
method: 'revoke',
|
||||
);
|
||||
try {
|
||||
var _localCallContext = await _endpointDispatch.getMethodCallContext(
|
||||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'assistantOperator',
|
||||
methodName: 'revoke',
|
||||
parameters: _ist.testObjectToJson({
|
||||
'account': account,
|
||||
'assistant': assistant,
|
||||
}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<void>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_ida.Future<List<_i5c40jr1.AssistantMonthFigures>> figures(
|
||||
_ist.TestSessionBuilder sessionBuilder, {
|
||||
required DateTime month,
|
||||
}) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
|
||||
endpoint: 'assistantOperator',
|
||||
method: 'figures',
|
||||
);
|
||||
try {
|
||||
var _localCallContext = await _endpointDispatch.getMethodCallContext(
|
||||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'assistantOperator',
|
||||
methodName: 'figures',
|
||||
parameters: _ist.testObjectToJson({'month': month}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<List<_i5c40jr1.AssistantMonthFigures>>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountEndpoint {
|
||||
_AccountEndpoint(
|
||||
this._endpointDispatch,
|
||||
|
|
|
|||
Loading…
Reference in a new issue