| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- """关联集合差量保存,仅使用独立内存 SQLite。"""
- from datetime import datetime
- import pytest
- from sqlalchemy import create_engine, text
- from sqlalchemy.orm import Session
- from app.crud import user, role
- from app.models.models import SysUserRole, SysUserPost, SysRole, SysRoleMenu, SysRoleDataScopeDetail, SysUser, SysPost, SysDept, SysMenu
- @pytest.fixture
- def db():
- engine = create_engine('sqlite:///:memory:')
- for model in (SysUserRole, SysUserPost, SysRole, SysRoleMenu, SysRoleDataScopeDetail, SysUser, SysPost, SysDept, SysMenu):
- model.__table__.create(engine)
- with engine.begin() as conn:
- conn.execute(text('CREATE UNIQUE INDEX uk_role_menu ON sys_role_menu(role_id, menu_id, del_flag)'))
- with Session(engine) as session:
- add(session, SysUser, 'u', user_no='u', nick_name='U')
- add(session, SysPost, 'p', post_name='post', post_code='P')
- add(session, SysMenu, 'm', menu_name='menu')
- add(session, SysDept, 'd', dept_name='dept')
- session.commit()
- yield session
- engine.dispose()
- def add(db, model, id, **fields):
- row = model(id=id, tenant_no='t', del_flag=0, create_time=datetime(2020, 1, 1), **fields)
- db.add(row)
- db.flush()
- return row
- def test_role_only_update_retains_posts_and_existing_identity(db):
- add(db, SysRole, 'r', role_name='role')
- add(db, SysRole, 'new', role_name='new')
- old = add(db, SysUserRole, 'ur', user_id='u', role_id='r')
- post = add(db, SysUserPost, 'up', user_id='u', post_id='p')
- user._replace_associations(db, 'u', 't', 'actor', role_ids=['r', 'new'])
- db.flush()
- assert old.del_flag == 0
- assert post.del_flag == 0
- assert old.create_time == datetime(2020, 1, 1)
- assert db.query(SysUserRole).filter_by(del_flag=0).count() == 2
- assert db.query(SysUserRole).count() == 2
- def test_post_diff_clear_and_restore_retains_ids_and_tenant(db):
- old = add(db, SysUserPost, 'up', user_id='u', post_id='p')
- foreign = add(db, SysUserPost, 'foreign', user_id='u', post_id='foreign')
- foreign.tenant_no = 'other'
- db.flush()
- user.set_user_post_ids_only(db, 'u', 't', ['p'], 'actor')
- db.flush()
- assert old.del_flag == 0
- assert foreign.del_flag == 0
- user.set_user_post_ids_only(db, 'u', 't', [], 'actor')
- db.flush()
- assert old.del_flag == 1
- user.set_user_post_ids_only(db, 'u', 't', ['p', 'p'], 'actor')
- db.flush()
- assert old.del_flag == 0
- assert db.query(SysUserPost).count() == 2
- def test_role_menus_noop_and_repeated_toggle_reuses_history(db):
- add(db, SysRole, 'r', role_name='role')
- old = add(db, SysRoleMenu, 'rm', role_id='r', menu_id='m')
- role.update_role_menus(db, 'r', ['m'], 't', 'actor')
- assert db.get(SysRoleMenu, 'rm') is not None
- assert old.del_flag == 0
- for _ in range(3):
- role.update_role_menus(db, 'r', [], 't', 'actor')
- assert old.del_flag == 1
- role.update_role_menus(db, 'r', ['m'], 't', 'actor')
- assert old.del_flag == 0
- assert db.query(SysRoleMenu).count() == 1
- def test_role_data_scope_complete_save_omitted_clears_and_diff_reuses_id(db):
- add(db, SysRole, 'r', role_name='role', data_scope='5')
- old = add(db, SysRoleDataScopeDetail, 'scope', role_id='r', scope_type='dept', scope_value='d')
- role.update_role_data_scope(db, 'r', '5', None, 't', 'actor')
- assert old.del_flag == 1
- role.update_role_data_scope(db, 'r', '5', [{'scopeType': 'dept', 'scopeValue': 'd'}], 't', 'actor')
- assert old.del_flag == 0
- assert db.query(SysRoleDataScopeDetail).count() == 1
- role.update_role_data_scope(db, 'r', '5', [], 't', 'actor')
- assert old.del_flag == 1
- def test_role_update_rejects_foreign_owner(db):
- owner = add(db, SysRole, 'r', role_name='role')
- owner.tenant_no = 'other'
- db.flush()
- with pytest.raises(ValueError):
- role.update_role_menus(db, 'r', ['m'], 't', 'actor')
- with pytest.raises(ValueError):
- role.update_role_data_scope(db, 'r', '5', [], 't', 'actor')
- @pytest.mark.parametrize('target_model, fields, kwargs', [
- (SysRole, {'role_name': 'foreign'}, {'role_ids': ['foreign']}),
- (SysPost, {'post_name': 'foreign', 'post_code': 'F'}, {'post_ids': ['foreign']}),
- ])
- def test_foreign_relation_target_rejected_before_changes(db, target_model, fields, kwargs):
- target = add(db, target_model, 'foreign', **fields)
- target.tenant_no = 'other'
- db.flush()
- with pytest.raises(ValueError):
- user._replace_associations(db, 'u', 't', 'actor', **kwargs)
- assert db.query(SysUserRole).count() == 0
- assert db.query(SysUserPost).count() == 0
- def test_foreign_scope_target_rejected(db):
- add(db, SysRole, 'r', role_name='role')
- target = db.get(SysDept, 'd')
- target.tenant_no = 'other'
- db.flush()
- with pytest.raises(ValueError):
- role.update_role_data_scope(db, 'r', '5', [{'scopeType':'dept','scopeValue':'d'}], 't', 'actor')
- def test_create_user_does_not_commit_parent_when_associations_fail(db, monkeypatch):
- monkeypatch.setattr(user, '_hash_password', lambda value: 'hashed')
- def fail(*args, **kwargs):
- raise ValueError('invalid association')
- monkeypatch.setattr(user, '_replace_associations', fail)
- with pytest.raises(ValueError):
- user.create_user(db, user_no='new-u', nick_name='new', password='password', tenant_no='t')
- db.rollback()
- assert db.query(SysUser).filter_by(user_no='new-u').first() is None
- def test_user_update_rejects_foreign_owner_before_header_change(db):
- with pytest.raises(ValueError):
- user.update_user(db, 'u', tenant_no='other', nick_name='hijacked', role_ids=[])
- assert db.get(SysUser, 'u').nick_name == 'U'
- def test_role_menu_rejects_foreign_target(db):
- add(db, SysRole, 'r', role_name='role')
- db.get(SysMenu, 'm').tenant_no = 'other'
- db.flush()
- with pytest.raises(ValueError):
- role.update_role_menus(db, 'r', ['m'], 't', 'actor')
- assert db.query(SysRoleMenu).count() == 0
|