feat(doctor,shell): ollama host-service suggestion + hub-update hint (0.80.0)
Some checks failed
Security / Security check (push) Failing after 2s
Some checks failed
Security / Security check (push) Failing after 2s
Doctor: when the system AI uses an Ollama endpoint that no declared host service covers (host:port match, /v1 suffix stripped), the services panel says so in one sentence with a one-click 'declare as host service' via the new DeclareService RPC — and states honestly that it takes effect after a daemon restart (the restart button sits on the same page). Pure suggestOllamaServiceEndpoint pins every branch. Shell: one slim, dismissible banner after connecting when the release manifest offers a newer hub version; dismissal is persisted per version so each release hints exactly once (pure shouldShowUpdateHint + a widget test through the fake hub). Deliberately manifest-based — Studio and hub versions are independent counters, so a direct comparison would be wrong; unreleased dev skew stays with the per-page classified states. The probe stays inert under the test probe override: its timeout timer leaked into hub_banner_test (the hermeticity class again). Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
351c5a82bc
commit
14f824b8ef
13 changed files with 703 additions and 135 deletions
149
test/ollama_service_suggestion_test.dart
Normal file
149
test/ollama_service_suggestion_test.dart
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
// The doctor's "declare the system AI's Ollama as a host service"
|
||||
// suggestion and the shell's one-time update hint — both are pure
|
||||
// functions so every branch is pinned here.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:chain_studio/data/hub.dart';
|
||||
import 'package:chain_studio/main.dart'
|
||||
show StudioApp, shouldShowUpdateHint;
|
||||
import 'package:chain_studio/pages/doctor.dart'
|
||||
show suggestOllamaServiceEndpoint;
|
||||
|
||||
import 'support/fake_hub.dart';
|
||||
|
||||
SystemAiStatus _ai({
|
||||
bool enabled = true,
|
||||
String provider = 'ollama',
|
||||
String endpoint = 'http://127.0.0.1:11434/v1',
|
||||
}) => SystemAiStatus(
|
||||
enabled: enabled,
|
||||
provider: provider,
|
||||
endpoint: endpoint,
|
||||
model: 'qwen2.5:14b',
|
||||
privacyMode: 'off',
|
||||
apiKeyEnv: '',
|
||||
);
|
||||
|
||||
void main() {
|
||||
group('suggestOllamaServiceEndpoint', () {
|
||||
test('suggests the base URL with the /v1 suffix stripped', () {
|
||||
expect(
|
||||
suggestOllamaServiceEndpoint(ai: _ai(), services: const []),
|
||||
'http://127.0.0.1:11434',
|
||||
);
|
||||
});
|
||||
|
||||
test('nothing to suggest when AI is off or another provider', () {
|
||||
expect(
|
||||
suggestOllamaServiceEndpoint(ai: _ai(enabled: false), services: const []),
|
||||
isNull,
|
||||
);
|
||||
expect(
|
||||
suggestOllamaServiceEndpoint(
|
||||
ai: _ai(provider: 'openai'),
|
||||
services: const [],
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
expect(
|
||||
suggestOllamaServiceEndpoint(ai: _ai(endpoint: ''), services: const []),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('a declared service on the same host:port silences it', () {
|
||||
expect(
|
||||
suggestOllamaServiceEndpoint(
|
||||
ai: _ai(),
|
||||
services: const [
|
||||
ServiceEntry(
|
||||
name: 'my-llm',
|
||||
endpoint: 'http://127.0.0.1:11434',
|
||||
tags: [],
|
||||
),
|
||||
],
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('a service on another port does not silence it', () {
|
||||
expect(
|
||||
suggestOllamaServiceEndpoint(
|
||||
ai: _ai(),
|
||||
services: const [
|
||||
ServiceEntry(
|
||||
name: 'judge-ner',
|
||||
endpoint: 'http://127.0.0.1:8756',
|
||||
tags: [],
|
||||
),
|
||||
],
|
||||
),
|
||||
'http://127.0.0.1:11434',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('the shell hints once and dismisses persistently', (
|
||||
tester,
|
||||
) async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
final fake = installFakeHub();
|
||||
fake.updateHint = const UpdateStatus(
|
||||
channel: 'stable',
|
||||
localVersion: '0.22.0',
|
||||
latestVersion: '0.23.0',
|
||||
updateAvailable: true,
|
||||
manifestReachable: true,
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
const StudioApp(
|
||||
initialThemeMode: ThemeModeValue.dark,
|
||||
initialLocale: Locale('de'),
|
||||
),
|
||||
);
|
||||
for (var i = 0; i < 8; i++) {
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
}
|
||||
expect(find.textContaining('0.23.0'), findsOneWidget);
|
||||
await tester.tap(find.text('Später'));
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
expect(find.textContaining('0.23.0'), findsNothing);
|
||||
// Dismissal is persisted per version.
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(prefs.getString('update.hint.dismissed'), '0.23.0');
|
||||
// Drain shell timers (a11y-suite pattern).
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
await tester.pump(const Duration(minutes: 1));
|
||||
});
|
||||
|
||||
group('shouldShowUpdateHint', () {
|
||||
test('hints once per release version', () {
|
||||
expect(
|
||||
shouldShowUpdateHint(latestVersion: '0.23.0', dismissedVersion: null),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
shouldShowUpdateHint(
|
||||
latestVersion: '0.23.0',
|
||||
dismissedVersion: '0.22.0',
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
shouldShowUpdateHint(
|
||||
latestVersion: '0.23.0',
|
||||
dismissedVersion: '0.23.0',
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
shouldShowUpdateHint(latestVersion: '', dismissedVersion: null),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -257,6 +257,20 @@ class FakeHubService extends Fake implements HubService {
|
|||
Future<List<N8nEndpointInfo>> listN8nEndpoints() =>
|
||||
_async('listN8nEndpoints', () => const []);
|
||||
|
||||
/// Scriptable update hint for the shell banner test.
|
||||
UpdateStatus? updateHint;
|
||||
|
||||
@override
|
||||
Future<UpdateStatus?> checkHubUpdate() async => updateHint;
|
||||
|
||||
@override
|
||||
Future<bool> declareService({
|
||||
required String name,
|
||||
required String endpoint,
|
||||
String healthPath = '',
|
||||
List<String> tags = const [],
|
||||
}) => _async('declareService', () => true);
|
||||
|
||||
@override
|
||||
Future<DoctorSnapshot> doctor() => _async(
|
||||
'doctor',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue