......................F.F................F.............................. [  9%]
.............F.......................................................... [ 18%]
...............................F........................................ [ 27%]
..F..................................................................... [ 37%]
........................................................................ [ 46%]
........................................................................ [ 55%]
........................................................................ [ 65%]
........................................................................ [ 74%]
........................................................................ [ 83%]
........................................................................ [ 93%]
....................................................                     [100%]
=================================== FAILURES ===================================
_ test_return_keeps_old_note_bindings_receipts_and_retired_repeat_rule_closed __

app = <fastapi.applications.FastAPI object at 0x10ee73800>

    def test_return_keeps_old_note_bindings_receipts_and_retired_repeat_rule_closed(app):
        c=client(app);j,people,_=setup(c,app);b=job(c);a=access(c,j);op=str(uuid.uuid4())
        assert action(c,j,op,{'binding':a['binding']},'cancel').status_code==200
        d,_,_=definition(c,app,j,people[0]);d=recurrence_action(c,d,{'action':'state','state':'retired','reason':'Close invented repeat definition before correction'})
>       move(c,j,b);middle=access(c,j,a['binding']['device_id']);move(c,j,j);returned=access(c,j,a['binding']['device_id'])
        ^^^^^^^^^^^

tests/test_customer_moves.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

c = <starlette.testclient.TestClient object at 0x10ee73e00>
j = {'id': '631e415a8ccbe20da76b6c509d6cc8fc', 'customer_id': '8282a759227ec00113f8d90064fef877'}
target = {'id': '9c97fbaed9f0c063435c2b0c64261183', 'customer_id': 'aa0856b704650f03819e7ffd7c484537'}
ids = None, key = None

    def move(c,j,target,ids=None,key=None):
        current=c.get('/api/jobs/'+j['id']).json()
        p=preview(c,selection(c,current,target,kind='jobs',job_ids=ids or [j['id']]))
>       assert p['can_confirm'],p['errors']
E       AssertionError: ['631e415a8ccbe20da76b6c509d6cc8fc: Work currently in progress']
E       assert False

tests/test_customer_moves.py:24: AssertionError
____ test_stale_or_invalid_batch_changes_nothing_and_retains_preview[note] _____

app = <fastapi.applications.FastAPI object at 0x10f3df920>, change = 'note'

    @pytest.mark.parametrize('change',['new_job','note','customer','portal','active_work','one_wrong_job'])
    def test_stale_or_invalid_batch_changes_nothing_and_retains_preview(app,change):
        c=client(app);a=job(c);b=job(c);second=work(c,customer(c,a['customer_id'])).json()
        move(c,a,b,ids=[a['id'],second['id']]);current=c.get('/api/jobs/'+a['id']).json()
        p=preview(c,selection(c,current,a,kind='jobs',job_ids=[a['id'],second['id']]))
        if change=='new_job':work(c,customer(c,b['customer_id']))
        elif change=='note':
            # A new association makes the saved dependency inventory stale.
            from test_device_notes import note
>           op,body,_=note(c,current);assert action(c,current,op,body).status_code==200
                      ^^^^^^^^^^^^^^^

tests/test_customer_moves.py:96: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
tests/test_device_notes.py:22: in note
    a=access(c,j)
      ^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

c = <starlette.testclient.TestClient object at 0x10f3de9c0>
j = {'id': 'ffe25aab5c257d7943ca5f4bea126f56', 'customer_id': 'd97b3b0c1785be82de409787d65f333d', 'service': 'Fit shelves', 'message': 'Two shelves in a demonstration room.', ...}
device = None

    def access(c,j,device=None):
        r=write(c,base(j)+'/note-access',{'device_id':device or str(uuid.uuid4())})
>       assert r.status_code==200,r.text
E       AssertionError: {"detail":"Field work is available for accepted, scheduled or in-progress jobs."}
E       assert 404 == 200
E        +  where 404 = <Response [404 Not Found]>.status_code

tests/test_device_notes.py:16: AssertionError
_ test_return_does_not_reopen_old_followup_or_permission_when_explicitly_unexcluded _

app = <fastapi.applications.FastAPI object at 0x10e2c8d70>

    def test_return_does_not_reopen_old_followup_or_permission_when_explicitly_unexcluded(app):
        c=client(app);a=job(c);b=job(c)
        edit(c,a,next_action='Call about invented original shelf size');old=next(i for i in items(c,'next_action') if i['job_id']==a['id']);decide(c,old,'dismiss')
        move(c,a,b);move(c,a,a);edit(c,a,followup_excluded=False)
        queue=items(c);assert next(i for i in queue if i['id']==old['id'])['eligible'] is False
        assert not any(i['eligible'] and i['job_id']==a['id'] for i in queue)
        edit(c,a,next_action='A new individually reviewed step after correction');fresh=next(i for i in items(c) if i['eligible'] and i['job_id']==a['id']);assert fresh['id']!=old['id']
        decide(c,fresh,'prepare');move(c,a,b);move(c,a,a);edit(c,a,followup_excluded=False)
>       assert all(not i['eligible'] for i in items(c) if i['job_id']==a['id'])
E       assert False
E        +  where False = all(<generator object test_return_does_not_reopen_old_followup_or_permission_when_explicitly_unexcluded.<locals>.<genexpr> at 0x10dec6ce0>)

tests/test_customer_moves.py:196: AssertionError
________ test_transferred_source_draft_requires_explicit_renewed_review ________

app = <fastapi.applications.FastAPI object at 0x10d03afc0>
tmp_path = PosixPath('/private/var/folders/kn/1_8nby0s6m7g8b2k6grh24wm0000gn/T/pytest-of-sandyrestall/pytest-62/test_transferred_source_draft_0')

    def test_transferred_source_draft_requires_explicit_renewed_review(app,tmp_path):
        from test_fieldwork import docact,doc as source_doc,suggested
        c=client(app);j,people,clock,tid=captured(c,app);d=draft(c,j).json();target=job(c)
>       plan=preview(c,selection(c,j,target));assert confirm(c,plan).status_code==200
                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AssertionError: assert 409 == 200
E        +  where 409 = <Response [409 Conflict]>.status_code
E        +    where <Response [409 Conflict]> = confirm(<starlette.testclient.TestClient object at 0x10ce01f10>, {'selection': {'kind': 'merge', 'source_id': 'ac872bb60ee04b0da55e8a5807523144', 'target_id': '5a0f24d585a7b70010ea945...ields': {'name': 'Alex Demo', 'email': 'alex@customer.example', 'phone': '000000', 'address': '1 Example Street'}, ...})

tests/test_customer_reviews.py:202: AssertionError
_______ test_v19_format12_archive_remains_readable_without_schema_change _______

tmp_path = PosixPath('/private/var/folders/kn/1_8nby0s6m7g8b2k6grh24wm0000gn/T/pytest-of-sandyrestall/pytest-62/test_v19_format12_archive_rema0')

    def test_v19_format12_archive_remains_readable_without_schema_change(tmp_path):
        app = create_app(tmp_path/'old-fixture')
        assert submit(app, payload(photos=[photo()])).status_code == 200
        original = recovery.backup(app.state.store.root, tmp_path/'current.zip')
        old = tmp_path/'old-format12.zip'
        def old_metadata(files, meta): meta['prototype_version'] = '0.19.0'
        sha = rewrite_archive(original['archive'], old, old_metadata)
>       assert recovery.verify(old, sha)['valid']
               ^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_enquiry_format_recovery.py:96: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
backend/recovery.py:436: in verify
    meta,summaries=unpack_validated(archive,sha256,Path(temporary))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend/recovery.py:402: in unpack_validated
    check_metadata(meta)
backend/recovery.py:191: in check_metadata
    require(meta['format']==FORMAT and type(meta['version']) is int and (meta['version'],meta['prototype_version']) in ((1,'0.4.0'),(2,'0.5.0'),(3,'0.6.0'),(4,'0.7.0'),(5,'0.8.0'),(6,'0.9.0'),(7,'0.10.0'),(8,'0.11.0'),(9,'0.12.0'),(10,'0.13.0'),(11,'0.14.0'),(12,'0.19.0'),(12,'0.20.0'),(13,'0.24.0')),'Unsupported recovery format/version.')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

condition = False, message = 'Unsupported recovery format/version.'

    def require(condition,message):
>       if not condition:raise RecoveryError(message)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       backend.recovery.RecoveryError: Unsupported recovery format/version.

backend/recovery.py:43: RecoveryError
__ test_format_eleven_migrates_without_fabricated_intake_or_photo_permission ___

tmp_path = PosixPath('/private/var/folders/kn/1_8nby0s6m7g8b2k6grh24wm0000gn/T/pytest-of-sandyrestall/pytest-62/test_format_eleven_migrates_wi0')

    def test_format_eleven_migrates_without_fabricated_intake_or_photo_permission(tmp_path):
        root=tmp_path/'old';application=create_app(root);owner=client(application);j=job(owner);p=reviewed(owner,j)
        before=table_rows(root,'oak');original=tmp_path/'original.zip';recovery.backup(root,original)
        def make_old(files,meta):
            meta.update(version=11,prototype_version='0.14.0')
            for name,data in list(files.items()):
                if not name.startswith('businesses/') or not name.endswith('.sqlite3'):continue
                path=tmp_path/(name.split('/')[1]+'.sqlite3');path.write_bytes(data)
                with sqlite3.connect(path) as db:
                    for table in TABLES:db.execute('DROP TABLE '+table)
                files[name]=path.read_bytes();meta['files'][name].update(bytes=len(files[name]),sha256=hashlib.sha256(files[name]).hexdigest())
>       old=tmp_path/'format11.zip';sha=rewrite_archive(original,old,make_old);assert recovery.verify(old,sha)['valid']
                                                                                      ^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_enquiry_recovery.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
backend/recovery.py:436: in verify
    meta,summaries=unpack_validated(archive,sha256,Path(temporary))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend/recovery.py:422: in unpack_validated
    summary=tenant_summary(stage/prefix/'business.sqlite3',bid,meta['dataset_id'],sanitised=True,format_version=meta['version'])
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend/recovery.py:128: in tenant_summary
    check_db(db,expected_schemas(format_version)[1])
backend/recovery.py:117: in check_db
    require(schema(db)==expected,'Database schema is unsupported or has unexpected tables, views, indexes or triggers.')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

condition = False
message = 'Database schema is unsupported or has unexpected tables, views, indexes or triggers.'

    def require(condition,message):
>       if not condition:raise RecoveryError(message)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       backend.recovery.RecoveryError: Database schema is unsupported or has unexpected tables, views, indexes or triggers.

backend/recovery.py:43: RecoveryError
=============================== warnings summary ===============================
../shs-v20-venv/lib/python3.12/site-packages/fastapi/testclient.py:1
  /private/tmp/shs-v20-venv/lib/python3.12/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

tests/test_photos.py::test_animation_and_pixel_limit
  /private/tmp/shs-v20-venv/lib/python3.12/site-packages/PIL/Image.py:3593: DecompressionBombWarning: Image size (12004000 pixels) exceeds limit of 12000000 pixels, could be decompression bomb DOS attack.
    warnings.warn(

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_customer_moves.py::test_return_keeps_old_note_bindings_receipts_and_retired_repeat_rule_closed
FAILED tests/test_customer_moves.py::test_stale_or_invalid_batch_changes_nothing_and_retains_preview[note]
FAILED tests/test_customer_moves.py::test_return_does_not_reopen_old_followup_or_permission_when_explicitly_unexcluded
FAILED tests/test_customer_reviews.py::test_transferred_source_draft_requires_explicit_renewed_review
FAILED tests/test_enquiry_format_recovery.py::test_v19_format12_archive_remains_readable_without_schema_change
FAILED tests/test_enquiry_recovery.py::test_format_eleven_migrates_without_fabricated_intake_or_photo_permission
6 failed, 766 passed, 2 warnings in 225.19s (0:03:45)
