mirror of
https://github.com/kuaifan/dootask.git
synced 2026-06-11 18:02:22 +00:00
单个创建:邮箱/昵称/初始密码 + 可选首登改密、职位、部门(多选,选子部门自动补选上级,并加入对应部门群)。 批量导入:上传 Excel/CSV → 预览逐行校验 → 确认后导入。职位为模板第4列(选填,逐行解析校验),部门在预览表按行勾选后由底部设置部门到选中写入;导入按行返回结果(全成功关弹窗+成功提示;含失败留弹窗显示失败明细;仅 success>0 才刷新列表)。 后端:User::createByAdmin 选项数组化 + 校验助手 assertValidProfession/assertValidDepartments;importUsers 逐行 department/profession;UsersController createuser/import;UserImport/UserImportTemplate(含职位列)。 测试:tests/Feature/AdminCreateUserTest、tests/Unit/UserImportParseTest。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
138 lines
5.6 KiB
PHP
138 lines
5.6 KiB
PHP
<?php
|
||
|
||
namespace Tests\Unit;
|
||
|
||
use App\Models\User;
|
||
use Tests\TestCase;
|
||
|
||
class UserImportParseTest extends TestCase
|
||
{
|
||
public function test_parse_skips_header_and_empty_rows()
|
||
{
|
||
$sheet = [
|
||
['邮箱', '昵称', '初始密码'], // 表头,应跳过
|
||
['a@test.local', '张三', 'Abc123456'],
|
||
['', '', ''], // 空行,应跳过
|
||
['b@test.local', '李四', 'Xyz123456'],
|
||
];
|
||
|
||
$rows = User::parseImportRows($sheet);
|
||
|
||
$this->assertCount(2, $rows);
|
||
$this->assertSame('a@test.local', $rows[0]['email']);
|
||
$this->assertSame('张三', $rows[0]['nickname']);
|
||
$this->assertSame('Abc123456', $rows[0]['password']);
|
||
$this->assertSame(2, $rows[0]['line']);
|
||
$this->assertSame(4, $rows[1]['line']);
|
||
}
|
||
|
||
public function test_parse_trims_cells()
|
||
{
|
||
$sheet = [
|
||
['邮箱', '昵称', '初始密码'],
|
||
[' a@test.local ', ' 张三 ', ' Abc123456 '],
|
||
];
|
||
|
||
$rows = User::parseImportRows($sheet);
|
||
|
||
$this->assertSame('a@test.local', $rows[0]['email']);
|
||
$this->assertSame('张三', $rows[0]['nickname']);
|
||
$this->assertSame('Abc123456', $rows[0]['password']);
|
||
}
|
||
|
||
public function test_validate_passes_for_valid_row()
|
||
{
|
||
$row = ['email' => 'ok@test.local', 'nickname' => '张三', 'password' => 'Abc123456'];
|
||
$this->assertNull(User::validateImportRow($row));
|
||
}
|
||
|
||
public function test_validate_requires_all_fields()
|
||
{
|
||
$this->assertSame('邮箱、昵称、初始密码均为必填', User::validateImportRow(['email' => '', 'nickname' => '张三', 'password' => 'Abc123456']));
|
||
$this->assertSame('邮箱、昵称、初始密码均为必填', User::validateImportRow(['email' => 'a@test.local', 'nickname' => '', 'password' => 'Abc123456']));
|
||
$this->assertSame('邮箱、昵称、初始密码均为必填', User::validateImportRow(['email' => 'a@test.local', 'nickname' => '张三', 'password' => '']));
|
||
}
|
||
|
||
public function test_validate_rejects_bad_email()
|
||
{
|
||
$this->assertSame('邮箱格式不正确', User::validateImportRow(['email' => 'not-an-email', 'nickname' => '张三', 'password' => 'Abc123456']));
|
||
}
|
||
|
||
public function test_validate_rejects_bad_nickname_length()
|
||
{
|
||
$this->assertSame('昵称需为2-20个字', User::validateImportRow(['email' => 'a@test.local', 'nickname' => '王', 'password' => 'Abc123456']));
|
||
$this->assertSame('昵称需为2-20个字', User::validateImportRow(['email' => 'a@test.local', 'nickname' => str_repeat('字', 21), 'password' => 'Abc123456']));
|
||
}
|
||
|
||
public function test_validate_rejects_short_password()
|
||
{
|
||
$this->assertNotNull(User::validateImportRow(['email' => 'a@test.local', 'nickname' => '张三', 'password' => '123']));
|
||
}
|
||
|
||
public function test_assert_valid_profession_passes_for_empty_and_normal()
|
||
{
|
||
// 空职位允许(可选字段),2/20 字边界与正常值允许;不抛异常即通过
|
||
User::assertValidProfession('');
|
||
User::assertValidProfession('工程'); // 恰好 2 字
|
||
User::assertValidProfession('工程师');
|
||
User::assertValidProfession(str_repeat('字', 20)); // 恰好 20 字
|
||
$this->assertTrue(true);
|
||
}
|
||
|
||
public function test_assert_valid_profession_rejects_too_short()
|
||
{
|
||
$this->expectException(\App\Exceptions\ApiException::class);
|
||
$this->expectExceptionMessage('职位/职称不可以少于2个字');
|
||
User::assertValidProfession('A');
|
||
}
|
||
|
||
public function test_assert_valid_profession_rejects_too_long()
|
||
{
|
||
$this->expectException(\App\Exceptions\ApiException::class);
|
||
$this->expectExceptionMessage('职位/职称最多只能设置20个字');
|
||
User::assertValidProfession(str_repeat('字', 21));
|
||
}
|
||
|
||
public function test_assert_valid_departments_normalizes_ids()
|
||
{
|
||
// 空/非数组 → 返回空数组
|
||
$this->assertSame([], User::assertValidDepartments([]));
|
||
$this->assertSame([], User::assertValidDepartments('not-array'));
|
||
// 去重 + 转 int + 过滤非正数(这些路径不查库)
|
||
$this->assertSame([3, 5], User::assertValidDepartments(['3', 3, 5, 0, -1]));
|
||
}
|
||
|
||
public function test_assert_valid_departments_rejects_over_limit()
|
||
{
|
||
// 超过 10 个(count 校验在查库之前)→ 抛异常
|
||
$this->expectException(\App\Exceptions\ApiException::class);
|
||
$this->expectExceptionMessage('最多只可加入10个部门');
|
||
User::assertValidDepartments(range(1, 11));
|
||
}
|
||
|
||
public function test_parse_reads_profession_column()
|
||
{
|
||
$sheet = [
|
||
['邮箱', '昵称', '初始密码', '职位'],
|
||
['a@test.local', '张三', 'Abc123456', '工程师'],
|
||
['b@test.local', '李四', 'Xyz123456'], // 无职位列 → profession 为空
|
||
];
|
||
$rows = User::parseImportRows($sheet);
|
||
$this->assertCount(2, $rows);
|
||
$this->assertSame('工程师', $rows[0]['profession']);
|
||
$this->assertSame('', $rows[1]['profession']);
|
||
}
|
||
|
||
public function test_validate_passes_for_empty_profession()
|
||
{
|
||
$row = ['email' => 'a@test.local', 'nickname' => '张三', 'password' => 'Abc123456', 'profession' => ''];
|
||
$this->assertNull(User::validateImportRow($row));
|
||
}
|
||
|
||
public function test_validate_rejects_bad_profession()
|
||
{
|
||
$row = ['email' => 'a@test.local', 'nickname' => '张三', 'password' => 'Abc123456', 'profession' => 'A'];
|
||
$this->assertSame('职位/职称不可以少于2个字', User::validateImportRow($row));
|
||
}
|
||
}
|