Some checks failed
Security / Security check (push) Failing after 1s
New Runs sidebar destination listing detached invocations (detach:true) with phase, current step, project and a Cancel button while pending/running. Workspace-scoped like Audit/Approvals, polls every 2s. Detached runs are opt-in (detached.enabled) — the empty state explains how to enable them. Inline help doc DE+EN. DetachedRun model + listDetachedRuns/cancelDetachedRun in HubService, backed by the SDK's listInvocations()/cancelInvocation(). flutter analyze clean; 29 tests green (sidebar Y-stability updated for the new destination, model mapping unit-tested). Screenshot verification (light+dark) deferred — shared desktop in use. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
96 lines
2.6 KiB
Dart
96 lines
2.6 KiB
Dart
// DetachedRun model — the phase mapping + cancellable predicate that
|
|
// drive the runs monitor's chip tone and the cancel-button visibility.
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'package:chain_studio/data/hub.dart';
|
|
// The generated enum type isn't re-exported by the SDK's public
|
|
// surface (only the message typedefs are), so build fixtures against
|
|
// the generated file directly — test-only.
|
|
import 'package:chain_client_sdk/src/generated/chain/v1/hub.pb.dart';
|
|
|
|
InvocationEntry _entry({
|
|
required String id,
|
|
required int phaseValue,
|
|
String flow = 'demo',
|
|
String project = 'general',
|
|
String step = '',
|
|
String error = '',
|
|
}) {
|
|
final status = InvocationStatus()
|
|
..phase = InvocationStatus_Phase.valueOf(phaseValue)!
|
|
..currentStep = step
|
|
..startedAt = '2026-07-12T10:00:00Z'
|
|
..error = error;
|
|
return InvocationEntry()
|
|
..invocationId = id
|
|
..flowName = flow
|
|
..project = project
|
|
..status = status;
|
|
}
|
|
|
|
void main() {
|
|
test('maps each wire phase to the model phase', () {
|
|
expect(
|
|
DetachedRun.fromEntry(_entry(id: 'a', phaseValue: 1)).phase,
|
|
DetachedPhase.pending,
|
|
);
|
|
expect(
|
|
DetachedRun.fromEntry(_entry(id: 'b', phaseValue: 2)).phase,
|
|
DetachedPhase.running,
|
|
);
|
|
expect(
|
|
DetachedRun.fromEntry(_entry(id: 'c', phaseValue: 3)).phase,
|
|
DetachedPhase.succeeded,
|
|
);
|
|
expect(
|
|
DetachedRun.fromEntry(_entry(id: 'd', phaseValue: 4)).phase,
|
|
DetachedPhase.failed,
|
|
);
|
|
expect(
|
|
DetachedRun.fromEntry(_entry(id: 'e', phaseValue: 5)).phase,
|
|
DetachedPhase.cancelled,
|
|
);
|
|
expect(
|
|
DetachedRun.fromEntry(_entry(id: 'f', phaseValue: 0)).phase,
|
|
DetachedPhase.unknown,
|
|
);
|
|
});
|
|
|
|
test('only pending and running runs are cancellable', () {
|
|
expect(
|
|
DetachedRun.fromEntry(_entry(id: 'a', phaseValue: 1)).isCancellable,
|
|
isTrue,
|
|
);
|
|
expect(
|
|
DetachedRun.fromEntry(_entry(id: 'b', phaseValue: 2)).isCancellable,
|
|
isTrue,
|
|
);
|
|
for (final terminal in [3, 4, 5]) {
|
|
expect(
|
|
DetachedRun.fromEntry(_entry(id: 'x', phaseValue: terminal))
|
|
.isCancellable,
|
|
isFalse,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('carries flow name, project, step and error through', () {
|
|
final run = DetachedRun.fromEntry(
|
|
_entry(
|
|
id: 'inv-1',
|
|
phaseValue: 4,
|
|
flow: 'intake',
|
|
project: 'client-a',
|
|
step: 'extract',
|
|
error: 'boom',
|
|
),
|
|
);
|
|
expect(run.id, 'inv-1');
|
|
expect(run.flowName, 'intake');
|
|
expect(run.project, 'client-a');
|
|
expect(run.currentStep, 'extract');
|
|
expect(run.error, 'boom');
|
|
expect(run.startedAt, isNotNull);
|
|
});
|
|
}
|