| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- """文档库权限/目录/分享的数据库集成测试;不连接开发库。"""
- import io
- from datetime import datetime
- import pytest
- from fastapi import HTTPException
- from sqlalchemy import create_engine
- from sqlalchemy.orm import sessionmaker
- from sqlalchemy.pool import StaticPool
- from app.models.models import SysDept, SysUser
- from app.models.doclib import DoclibFolder, DoclibFile, DoclibShare
- from app.services.doclib_service import DoclibService
- @pytest.fixture
- def db():
- engine = create_engine('sqlite://', poolclass=StaticPool, connect_args={'check_same_thread': False})
- for table in [SysDept.__table__, SysUser.__table__, DoclibFolder.__table__, DoclibFile.__table__, DoclibShare.__table__]:
- table.create(engine)
- session = sessionmaker(bind=engine, expire_on_commit=False)()
- for uid, tenant in [('owner', 'a'), ('reader', 'a'), ('stranger', 'a'), ('admin', 'a'), ('outsider', 'b')]:
- session.add(SysUser(id=uid, tenant_no=tenant, user_no=uid, nick_name=uid, status='1'))
- session.commit()
- yield session
- session.close()
- engine.dispose()
- def svc(db, uid='owner', tenant='a', role='admin'):
- return DoclibService(db, {'user_id': uid, 'tenant_no': tenant, 'role': role, 'region_id': 'dept'})
- def seed_file(db, service=None, folder_id='', name='制度.pdf'):
- service = service or svc(db)
- return service.create_file(name=name, folder_id=folder_id, size=12, content_type='application/pdf', object_key='opaque/'+name)
- def denied(fn, status=404):
- with pytest.raises(HTTPException) as exc:
- fn()
- assert exc.value.status_code == status
- def test_private_by_default_and_no_storage_url(db):
- doc = seed_file(db)
- assert len(svc(db).list_files()['list']) == 1
- assert svc(db, 'stranger').list_files(scope='shared')['total'] == 0
- denied(lambda: svc(db, 'stranger').get_file(doc['id']))
- assert 'objectKey' not in doc and 'url' not in doc
- assert doc['canManage'] is True and doc['shares'] == []
- def test_share_receiver_read_only_and_revocation(db):
- doc = seed_file(db)
- svc(db).set_shares(doc['id'], ['reader'])
- reader = svc(db, 'reader')
- assert reader.list_files(scope='shared')['total'] == 1
- assert reader.get_file(doc['id'])['canManage'] is False
- denied(lambda: reader.set_shares(doc['id'], ['stranger']), 403)
- denied(lambda: reader.delete_files([doc['id']]), 403)
- svc(db).set_shares(doc['id'], [])
- assert reader.list_files(scope='shared')['total'] == 0
- denied(lambda: reader.download_record(doc['id']))
- def test_admin_only_inside_current_tenant(db):
- local = seed_file(db)
- remote = seed_file(db, svc(db, 'outsider', 'b'))
- admin = svc(db, 'admin', role='super_admin')
- assert admin.list_files(scope='all')['total'] == 1
- assert admin.get_file(local['id'])['canManage'] is True
- denied(lambda: admin.get_file(remote['id']))
- denied(lambda: svc(db).list_files(scope='all'), 403)
- admin.delete_files([local['id']])
- assert db.get(DoclibFile, local['id']).del_flag == 1
- denied(lambda: svc(db).get_file(local['id']))
- def test_cross_tenant_and_inactive_share_is_atomic(db):
- doc = seed_file(db)
- svc(db).set_shares(doc['id'], ['reader'])
- denied(lambda: svc(db).set_shares(doc['id'], ['stranger', 'outsider']), 400)
- assert [u['id'] for u in svc(db).get_file(doc['id'])['shares']] == ['reader']
- db.get(SysUser, 'stranger').status = '0'
- db.commit()
- denied(lambda: svc(db).set_shares(doc['id'], ['stranger']), 400)
- assert {u['id'] for u in svc(db).users()} == {'reader', 'admin'}
- def test_nested_folder_tree_shared_only_relevant_path(db):
- service = svc(db)
- parent = service.create_folder('公司制度', '')
- child = service.create_folder('人事', parent['folderId'])
- hidden = service.create_folder('财务', parent['folderId'])
- doc = seed_file(db, folder_id=child['folderId'])
- seed_file(db, folder_id=hidden['folderId'], name='预算.xlsx')
- service.set_shares(doc['id'], ['reader'])
- reader = svc(db, 'reader')
- shared = next(n for n in reader.tree()['nodes'] if n['scope'] == 'shared')
- assert shared['count'] == 1
- assert [n['name'] for n in shared['children']] == ['公司制度']
- assert [n['name'] for n in shared['children'][0]['children']] == ['人事']
- assert reader.get_file(doc['id'])['folderPath'] == '公司制度 / 人事'
- assert reader.list_files(scope='shared', folder_id=hidden['folderId'])['total'] == 0
- denied(lambda: reader.create_folder('越权子目录', parent['folderId']), 403)
- denied(lambda: reader.create_file(name='bad.txt', folder_id=child['folderId'], size=1, content_type='text/plain', object_key='x'), 403)
- def test_folder_rename_empty_delete_and_duplicates(db):
- service = svc(db)
- folder = service.create_folder('资料', '')
- fid = folder['folderId']
- denied(lambda: service.create_folder('资料', ''), 409)
- assert service.rename_folder(fid, '新资料')['fullPath'] == '新资料'
- doc = seed_file(db, folder_id=fid)
- denied(lambda: service.delete_folder(fid), 409)
- service.delete_files([doc['id']])
- service.delete_folder(fid)
- assert db.get(DoclibFolder, fid).del_flag == 1
- assert service.create_folder('新资料', '')['folderId'] != fid
- def test_parent_cannot_be_deleted_with_child(db):
- service = svc(db)
- f = service.create_folder('父目录', '')
- service.create_folder('子目录', f['folderId'])
- denied(lambda: service.delete_folder(f['folderId']), 409)
- def test_batch_delete_denied_is_all_or_nothing(db):
- mine = seed_file(db)
- other = seed_file(db, svc(db, 'reader'), name='other.txt')
- svc(db, 'reader').set_shares(other['id'], ['owner'])
- denied(lambda: svc(db).delete_files([mine['id'], other['id']]), 403)
- assert svc(db).get_file(mine['id'])['name'] == '制度.pdf'
- def test_paging_search_sort_and_soft_deleted_shares(db):
- service = svc(db)
- old = seed_file(db, name='旧制度.pdf')
- new = seed_file(db, name='新制度.pdf')
- db.get(DoclibFile, old['id']).create_time = datetime(2025, 1, 1)
- db.commit()
- page = service.list_files(keyword='制度', page=1, page_size=1)
- assert page['total'] == 2 and page['list'][0]['id'] == new['id']
- service.set_shares(new['id'], ['reader', 'reader'])
- assert len(service.get_file(new['id'])['shares']) == 1
- service.set_shares(new['id'], [])
- assert db.query(DoclibShare).filter_by(file_id=new['id'], del_flag=0).count() == 0
- service.set_shares(new['id'], ['reader'])
- assert svc(db, 'reader').list_files(scope='shared')['total'] == 1
- def test_schema_rejects_client_tenant_and_invalid_names():
- from pydantic import ValidationError
- from app.schemas.doclib import FolderCreateBody, FolderUpdateBody, ShareBody
- with pytest.raises(ValidationError):
- FolderCreateBody(name='资料', tenant_no='b')
- for name in ['', ' ', '../secret', 'A/B', 'A\\B']:
- with pytest.raises(ValidationError):
- FolderUpdateBody(name=name)
- assert FolderCreateBody(name=' 资料 ').name == '资料'
- assert ShareBody(userIds=[]).userIds == []
|