Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,7 @@ mdast_cli \
**Parameter:** `--architecture_id <id>`

Select the target architecture/OS version for scanning:
- If not specified, defaults to Android 11 or iOS 14 (depending on file type)
- If not specified, defaults to Android 11 or prefers iOS 16, then iOS 15, then iOS 14 (depending on file type and the server catalogue)
- Use specific architecture ID for testing on different OS versions
- Available architectures depend on your DAST platform configuration

Expand Down
2 changes: 1 addition & 1 deletion mdast_cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '2026.9.2'
__version__ = '2026.10.4'
3 changes: 2 additions & 1 deletion mdast_cli/helpers/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ class DastState:

# Default architecture names for auto-selection
DEFAULT_ANDROID_ARCHITECTURE = 'Android 11'
DEFAULT_IOS_ARCHITECTURE = 'iOS 14'
DEFAULT_IOS_ARCHITECTURE = 'iOS 16'
IOS_ARCHITECTURE_PRIORITY = (DEFAULT_IOS_ARCHITECTURE, 'iOS 15', 'iOS 14')

# Timeout constants (in seconds)
TRY = 360
Expand Down
6 changes: 3 additions & 3 deletions mdast_cli/mdast_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from mdast_cli.distribution_systems.nexus2 import Nexus2Repository
from mdast_cli.distribution_systems.rumarket import rumarket_download_app
from mdast_cli.distribution_systems.rustore import rustore_download_app
from mdast_cli.helpers.const import (ANDROID_EXTENSIONS, DEFAULT_ANDROID_ARCHITECTURE, DEFAULT_IOS_ARCHITECTURE,
from mdast_cli.helpers.const import (ANDROID_EXTENSIONS, DEFAULT_ANDROID_ARCHITECTURE, IOS_ARCHITECTURE_PRIORITY,
END_SCAN_TIMEOUT, LONG_TRY, REPORT_TIMEOUT, SLEEP_TIMEOUT, TRY, DastState,
DastStateDict)
from mdast_cli.helpers.exit_codes import ExitCode
Expand Down Expand Up @@ -542,8 +542,8 @@ def run_monolith_flow(arguments, url, token, company_id, app_file, appstore_app_
architecture = next((arch.get('id') for arch in architectures
if isinstance(arch, dict) and arch.get('name') == DEFAULT_ANDROID_ARCHITECTURE), None)
if file_extension == '.ipa':
architecture = next((arch.get('id') for arch in architectures
if isinstance(arch, dict) and arch.get('name') == DEFAULT_IOS_ARCHITECTURE), None)
architecture = next((arch.get('id') for name in IOS_ARCHITECTURE_PRIORITY for arch in architectures
if isinstance(arch, dict) and arch.get('name') == name), None)
if architecture is None:
logger.error("Cannot create scan - no suitable architecture for this app, try to set it manually with --architecture_id")
sys.exit(ExitCode.INVALID_ARGS)
Expand Down
11 changes: 5 additions & 6 deletions mdast_cli/ms_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import requests

from mdast_cli.helpers.const import (ACTIVE_STAGES, ANDROID_EXTENSIONS, DEFAULT_ANDROID_ARCHITECTURE,
DEFAULT_IOS_ARCHITECTURE, END_SCAN_TIMEOUT, LONG_TRY, OS_ANDROID,
IOS_ARCHITECTURE_PRIORITY, END_SCAN_TIMEOUT, LONG_TRY, OS_ANDROID,
OS_IOS, PRE_START_STAGES, REPORT_TIMEOUT, SLEEP_TIMEOUT,
TERMINAL_SCAN_PAIRS, TRY, UPLOAD_TIMEOUT_ENV_VAR, UPLOAD_TIMEOUT_MAX,
UPLOAD_TIMEOUT_MIN, ENGINE_ACTIVE_STATUS, ScanStage, ScanStageStatus)
Expand Down Expand Up @@ -92,12 +92,11 @@ def resolve_ms_os_version(architectures, platform):
if not candidates:
return None

preferred_name = {
OS_ANDROID: DEFAULT_ANDROID_ARCHITECTURE,
OS_IOS: DEFAULT_IOS_ARCHITECTURE,
}.get(platform)
preferred_names = (IOS_ARCHITECTURE_PRIORITY if platform == OS_IOS
else (DEFAULT_ANDROID_ARCHITECTURE,))
selected = next(
(architecture for architecture in candidates if architecture.get('name') == preferred_name),
(architecture for name in preferred_names for architecture in candidates
if architecture.get('name') == name),
candidates[0],
)
return str(selected['os_version']).strip()
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
setup(
name="mdast_cli",

version='2026.10.3',
version='2026.10.4',

python_requires='>=3.12',

Expand Down
35 changes: 35 additions & 0 deletions specs/ios-default-priority.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Приоритет iOS при автоматическом выборе архитектуры

## Требование

По решению пользователя от 08.10.2026 порядок выбора: iOS 16, затем iOS 15,
затем iOS 14. Сейчас CLI предпочитает iOS 14 независимо от порядка каталога.

## Границы

- Общий дефолт iOS становится 16.
- Kubernetes и монолит выбирают первую присутствующую архитектуру из порядка 16/15/14.
- Порядок записей каталога не влияет на этот приоритет.
- Явный architecture_id в монолите сохраняет приоритет над автоматическим выбором.
- Android, обработка движков и контракт API не меняются.
- Если ни одной из трёх архитектур нет, сохраняется прежнее поведение:
Kubernetes берёт первую подходящую iOS-запись, монолит просит явный architecture_id.
- Изменение не добавляет выбор ОС по доступности устройств организации.

## Приёмка

Тесты покрывают разные порядки каталога, fallback 16 → 15 → 14,
пустой/неподходящий каталог, прежний Android-дефолт и явный выбор в монолите.
По подтверждению пользователя публикуется релиз 2026.10.4 в GitHub, PyPI и Docker.
Версии setup.py и mdast_cli/__init__.py синхронизируются.
Перед публикацией должен пройти полный CI; после публикации проверяется версия пакета и образа.

## Результаты проверки 08.10.2026

- 128 тестов прошли, git diff --check без ошибок.
- Изменённая локальная CLI выбрала iOS 16 на dev, скан 20399.
- Скан дошёл до SUCCESS/PARTIAL_COMPLETE, exit code 0, JSON и PDF получены.
- Неполнота вызвана отсутствием зарегистрированного сервиса app_dump.
Также зафиксированы падения контрольного приложения. Эти проблемы
не исправляются изменением порядка выбора ОС.
- Полный локальный набор: 410 passed, 1 skipped. Публикация 2026.10.4 выполняется после CI.
18 changes: 18 additions & 0 deletions tests/test_ms_architecture.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Unit tests for microservices OS-version selection (STG-4588)."""

from itertools import permutations

import pytest

from mdast_cli.helpers.const import OS_ANDROID, OS_IOS
Expand Down Expand Up @@ -44,3 +46,19 @@ def test_selects_version_from_paginated_scanyon_catalogue():
@pytest.mark.parametrize('architectures', [None, {}, [], [{'type': 'ANDROID', 'os_version': ''}]])
def test_missing_platform_version_returns_none(architectures):
assert resolve_ms_os_version(architectures, OS_ANDROID) is None


@pytest.mark.parametrize('versions,expected', [
*[(versions, '16') for versions in permutations(('14', '15', '16'))],
(('14', '15'), '15'),
(('15', '14'), '15'),
(('14',), '14'),
(('26.1', '14', '15', '16'), '16'),
(('26.1',), '26.1'),
((), None),
])
@pytest.mark.parametrize('paginated', [False, True])
def test_ios_priority(versions, expected, paginated):
catalogue = [{'type': 'IOS', 'name': f'iOS {v}', 'os_version': v} for v in versions]
payload = {'items': catalogue} if paginated else catalogue
assert resolve_ms_os_version(payload, OS_IOS) == expected
40 changes: 40 additions & 0 deletions tests/test_smoke_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""
import json
import os
from itertools import permutations

import pytest
import responses
Expand All @@ -16,6 +17,45 @@
pytestmark = pytest.mark.smoke


@pytest.mark.parametrize('versions,explicit,expected', [
*[(versions, None, 16) for versions in permutations((14, 15, 16))],
((14, 15), None, 15),
((15, 14), None, 15),
((14,), None, 14),
((14, 15, 16), 14, 14),
((26,), None, None),
((), None, None),
])
def test_monolith_ios_priority(mocked_responses, monkeypatch, tmp_path,
monolith_mode, versions, explicit, expected):
ipa = tmp_path / 'app.ipa'
ipa.write_bytes(b'local test payload')
mocked_responses.add(responses.GET, f'{REST_URL}/architectures/', json=[
{'id': v, 'name': f'iOS {v}', 'type': 2} for v in versions
])
mocked_responses.add(responses.GET, f'{REST_URL}/organizations/1/engines/', json=[
{'architecture': expected, 'state': 3},
])
mocked_responses.add(responses.GET, f'{REST_URL}/organizations/1/applications/',
json=[{'id': 10}])
mocked_responses.add(responses.POST, f'{REST_URL}/organizations/1/dasts/',
json={'id': 77}, status=201)
mocked_responses.add(responses.POST, f'{REST_URL}/dasts/77/start/', json={})
argv = ['--distribution_system', 'file', '--file_path', str(ipa),
'--url', BASE_URL, '--company_id', '1', '--token', TOKEN, '--nowait']
if explicit is not None:
argv += ['--architecture_id', str(explicit)]
result = run_main(monkeypatch, argv)
if expected is None:
assert result == 2
assert len(mocked_responses.calls) == 1
return
assert result == 0
created = next(call for call in mocked_responses.calls
if call.request.url.endswith('/organizations/1/dasts/'))
assert json.loads(created.request.body)['architecture_id'] == expected


def register_ms_happy_path(rsps, apk_md5, with_testcase=True, paginated_architectures=False):
architectures = [
{'id': 1, 'type': 'ANDROID', 'os_version': '11', 'name': 'Android 11', 'description': 'API 30'},
Expand Down