"""Agent binary upload version gating: PE-fallback and precedence. The upload route no longer rejects the version at raw input. A typed value is only a hint; the dotted-numeric FILE version in the binary's PE resource is the authority. The route falls back to it when the typed value is missing or not dotted-numeric, and only refuses when neither yields a usable version. Building a real PE with a RT_VERSION resource inline is impractical (see the agentstore unit tests), so these API tests monkeypatch read_pe_file_version to stand in for the parser. PE-magic bytes keep looks_like_pe happy so the request reaches the version gate. """ import io from plugins.endpoints.api import agentstore from plugins.endpoints.api import agentupload from shopdb.config import Config UPLOAD_URL = '/api/endpoints/siteagent/binaries/{}' _PE_BYTES = b'MZ' + b'\x00' * 128 def _upload(client, headers, monkeypatch, detected, typed=None): monkeypatch.setattr(agentstore, 'read_pe_file_version', lambda data: detected) form = {'file': (io.BytesIO(_PE_BYTES), 'shopdbagent.exe')} if typed is not None: form['version'] = typed return client.post( UPLOAD_URL.format('agent'), data=form, content_type='multipart/form-data', headers=headers) def test_upload_falls_back_to_pe_version_when_typed_is_missing( client, db, app, auth_headers, monkeypatch): response = _upload(client, auth_headers, monkeypatch, '1.5.0.0') assert response.status_code == 200, response.get_json() payload = response.get_json()['data'] assert payload['current'] == '1.5.0.0' assert any(v['version'] == '1.5.0.0' for v in payload['versions']) def test_upload_falls_back_when_typed_is_not_dotted_numeric( client, db, app, auth_headers, monkeypatch): response = _upload(client, auth_headers, monkeypatch, '1.5.0.0', typed='1.5.0+1a1bf0640f956634dada1b34840') assert response.status_code == 200, response.get_json() assert response.get_json()['data']['current'] == '1.5.0.0' def test_upload_prefers_valid_typed_version_over_pe( client, db, app, auth_headers, monkeypatch): response = _upload(client, auth_headers, monkeypatch, '9.9.9.9', typed='1.6.3') assert response.status_code == 200, response.get_json() assert response.get_json()['data']['current'] == '1.6.3' def test_upload_rejects_when_neither_typed_nor_pe_is_usable( client, db, app, auth_headers, monkeypatch): response = _upload(client, auth_headers, monkeypatch, None) assert response.status_code == 400 assert 'dotted-numeric' in response.get_json()['data']['error']['message'] def test_upload_cap_admits_a_self_contained_single_file_publish(): """The tray publishes ~69 MB; the cap must clear a real binary. This is the bug that presented as "upload does not detect version": the size check returns before the PE is parsed, so an oversized file looks like a parser failure to the operator. Pinned at 100 MB rather than the observed 69 MB so runtime growth does not silently re-break it. """ assert agentupload._MAX_UPLOAD_BYTES >= 100 * 1024 * 1024 def test_request_cap_stays_above_the_agent_upload_cap(): """MAX_CONTENT_LENGTH bounds the whole multipart envelope, not the file. Setting the two equal rejects a file sitting exactly at the agent cap, because the boundaries and form fields push the request over. They must move together, with real headroom between them. """ envelope = Config.MAX_CONTENT_LENGTH assert envelope > agentupload._MAX_UPLOAD_BYTES assert envelope - agentupload._MAX_UPLOAD_BYTES >= 1024 * 1024