mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-26 19:36:35 +08:00
fix(migration): treat an unselected runner id as not_legacy
A saved pipeline may carry the post-migration shape
{'ai': {'runner': {'id': ''}, 'runner_config': {}}} while no runner has been
selected yet. The planner matched the empty id against the certified
'plugin:author/name/component' form, failed, and returned a malformed-id
blocker, so the whole 'migrate all' batch stopped with both pipelines reported
as blocked even though there was no legacy section and nothing to convert.
A blank id with no legacy runner section now reports not_legacy: no work is
needed and no target is synthesized. A blank id that still coexists with a
legacy section remains an explicit invalid_runner_id blocker, because that
ambiguous state must be resolved by the operator rather than guessed.
This commit is contained in:
@@ -634,6 +634,12 @@ def plan_legacy_pipeline(config, extensions_preferences=None) -> dict:
|
||||
return _block(result, 'mixed_runner_selection', 'ai.runner')
|
||||
if 'id' in selection:
|
||||
current = selection['id']
|
||||
# A blank runner id paired with no legacy runner section means the saved
|
||||
# pipeline simply has no runner selected. There is nothing to migrate and
|
||||
# nothing to convert, so report not_legacy instead of failing the whole
|
||||
# batch with a malformed-id blocker.
|
||||
if type(current) is str and not current.strip() and not any(legacy in ai for legacy in _TARGETS):
|
||||
return result
|
||||
if type(current) is not str or not re.fullmatch(r'plugin:[^/\s]+/[^/\s]+/[^/\s]+', current):
|
||||
return _block(result, 'invalid_runner_id', 'ai.runner.id')
|
||||
result['state'] = 'already_current'
|
||||
|
||||
@@ -641,12 +641,40 @@ def test_dify_saved_timeout_is_not_blindly_activated(timeout):
|
||||
assert {'code': 'dify.timeout_default', 'field': 'ai.dify-service-api.timeout'} in result['warnings']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('current', [None, '', False, [], 'plugin:bad', ' plugin:a/b/c', 'plugin:a/b/c/extra'])
|
||||
@pytest.mark.parametrize('current', [None, False, [], 'plugin:bad', ' plugin:a/b/c', 'plugin:a/b/c/extra'])
|
||||
def test_malformed_current_id_is_not_already_current(current):
|
||||
source = {'ai': {'runner': {'id': current}}}
|
||||
assert_block(plan(source), 'invalid_runner_id', 'ai.runner.id')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('blank', ['', ' '])
|
||||
def test_blank_current_id_without_legacy_section_is_not_legacy(blank):
|
||||
"""A saved pipeline that simply has no runner selected is not legacy.
|
||||
|
||||
It has no legacy runner section to convert and no target to synthesize, so it
|
||||
must report not_legacy instead of blocking the whole batch with a
|
||||
malformed-id diagnostic.
|
||||
"""
|
||||
source = {'ai': {'runner': {'id': blank, 'expire-time': 0}, 'runner_config': {}}}
|
||||
result = plan(source)
|
||||
assert result['state'] == 'not_legacy'
|
||||
assert result['config'] is None
|
||||
assert result['blockers'] == []
|
||||
assert result['changed_paths'] == []
|
||||
assert result['target_plugin'] is None
|
||||
|
||||
|
||||
def test_blank_current_id_with_legacy_section_stays_blocked():
|
||||
"""A blank id cannot silently coexist with a legacy section.
|
||||
|
||||
An unselected plugin runner plus a legacy section is an ambiguous state that
|
||||
the operator must resolve, so it keeps the malformed-id blocker.
|
||||
"""
|
||||
source = source_for()
|
||||
source['ai']['runner'] = {'id': '', 'expire-time': 0}
|
||||
assert_block(plan(source), 'invalid_runner_id', 'ai.runner.id')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('runner', TARGETS)
|
||||
def test_deterministic_results_and_input_nonmutation_for_all_nine(runner):
|
||||
source = source_for(runner)
|
||||
|
||||
Reference in New Issue
Block a user