import asyncio
from io import BytesIO
from types import SimpleNamespace
import pytest
from fastapi import HTTPException, UploadFile
from app.vendors.backend import resources as r
def run(call):
return asyncio.run(call)
def test_unknown_vendor_blocks_queries(monkeypatch):
monkeypatch.setattr(r, 'execute_query_single', lambda *args: None)
with pytest.raises(HTTPException) as error:
run(r.files(999))
assert error.value.status_code == 404
def test_upload_limits_and_safe_filename(monkeypatch):
saved = []
def query(sql, params):
if sql.startswith('SELECT'): return {'id': 1}
saved.append(params)
return {'id': 2}
monkeypatch.setattr(r, 'execute_query_single', query)
request = SimpleNamespace(state=SimpleNamespace(user_id=3))
run(r.upload_file(1, request, UploadFile(filename='../test.pdf', file=BytesIO(b'abc'))))
assert saved[0][1] == 'test.pdf'
assert saved[0][3] == 3
assert saved[0][-1] == 3
monkeypatch.setattr(r, 'MAX_FILE_BYTES', 2)
with pytest.raises(HTTPException) as error:
run(r.upload_file(1, request, UploadFile(filename='big', file=BytesIO(b'abc'))))
assert error.value.status_code == 413
assert len(saved) == 1
def test_db_only_attachment_download_is_vendor_scoped(monkeypatch):
captured = []
def query(sql, params):
if sql.startswith('SELECT id FROM vendors'): return {'id': 1}
captured.append((sql, params))
return {'filename': 'bilag.pdf', 'content_data': memoryview(b'pdf'), 'file_path': None}
monkeypatch.setattr(r, 'execute_query_single', query)
result = run(r.download_file(1, 'email', 7))
assert result.body == b'pdf'
assert captured[0][1] == (1, 7)
assert 'e.supplier_id=%s' in captured[0][0]
assert result.headers['content-disposition'].startswith('attachment;')
def test_missing_foreign_attachment_is_not_downloaded(monkeypatch):
monkeypatch.setattr(r, 'execute_query_single', lambda sql, params: {'id':1} if sql.startswith('SELECT id FROM vendors') else None)
with pytest.raises(HTTPException) as error:
run(r.download_file(1, 'email', 8))
assert error.value.status_code == 404
def test_email_preview_is_pure_and_sanitized(monkeypatch):
monkeypatch.setattr(r, 'execute_query_single', lambda sql, params: {'id':1} if sql.startswith('SELECT id FROM vendors') else {'body_html':'
Mail
'})
monkeypatch.setattr(r, 'execute_update', lambda *args: pytest.fail('preview must not write'))
result = run(r.email_detail(1, 7))
assert result['body_html'] == 'Mail
'
def test_cases_do_not_include_every_customer_case(monkeypatch):
monkeypatch.setattr(r, 'execute_query_single', lambda *args: {'id':1})
seen = []
monkeypatch.setattr(r, 'execute_query', lambda sql, params: seen.append((sql,params)) or [])
run(r.cases(1))
assert 'customer_vendor_links' not in seen[0][0]
assert 'sag_emails' in seen[0][0]
assert seen[0][1] == (1,1,1)
def test_invalid_case_never_creates_link(monkeypatch):
monkeypatch.setattr(r, 'execute_query_single', lambda sql, params: {'id':1} if sql.startswith('SELECT id FROM vendors') else None)
monkeypatch.setattr(r, 'execute_query', lambda *args: pytest.fail('no writes on invalid case'))
with pytest.raises(HTTPException): run(r.link_case(1, r.CaseLink(sag_id=9)))
def test_contact_link_keeps_existing_contact_and_scopes_removal(monkeypatch):
monkeypatch.setattr(r, 'execute_query_single', lambda *args: {'id':1})
seen = []
monkeypatch.setattr(r, 'execute_query', lambda sql, params: seen.append((sql,params)))
monkeypatch.setattr(r, 'execute_update', lambda sql, params: seen.append((sql,params)))
run(r.link_contact(1,r.ContactLink(contact_id=3,role=' Support ')))
run(r.unlink_contact(1,3))
assert seen[0][1] == (1,3,'Support',None,None)
assert seen[1][1] == (1,3)
assert 'DELETE FROM vendor_contacts' in seen[1][0]
def test_pdf_can_open_inline_but_default_is_download():
content = b'%PDF-1.7\nfixture'
opened = r.download(content, 'bilag.pdf', inline=True)
saved = r.download(content, 'bilag.pdf')
assert opened.headers['content-type'] == 'application/pdf'
assert opened.headers['content-disposition'].startswith('inline;')
assert saved.headers['content-disposition'].startswith('attachment;')
def test_active_html_is_never_opened_inline():
response = r.download(b'', 'fil.html', inline=True)
assert response.headers['content-type'] == 'application/octet-stream'
assert response.headers['content-disposition'].startswith('attachment;')
def test_text_opens_as_plain_text():
response = r.download(b'', 'noter.txt', inline=True)
assert response.headers['content-type'].startswith('text/plain')
assert response.headers['content-disposition'].startswith('inline;')
def test_contact_primary_is_optional_and_explicit(monkeypatch):
monkeypatch.setattr(r, 'execute_query_single', lambda *args: {'id':1})
seen=[]
monkeypatch.setattr(r,'execute_query',lambda sql,params:seen.append((sql,params)))
run(r.link_contact(1,r.ContactLink(contact_id=3,is_primary=True)))
assert seen[0][1][-2:]==(True,True)
assert 'COALESCE(%s,vendor_contacts.is_primary)' in seen[0][0]