88 Commits
Author SHA1 Message Date
nanxun f6ff17ed02 Merge pull request 'main' (#54) from main into y1
Reviewed-on: #54
2026-01-30 00:45:07 +08:00
nanxun c265868a51 Merge pull request '前端:修复wss连接失败的问题' (#53) from feature-nxdev into main
Reviewed-on: #53
2026-01-29 23:44:46 +08:00
nanxun 7ebf23ddd8 前端:修复wss连接失败的问题 2026-01-29 23:43:50 +08:00
nanxun 8938dd40d4 Merge pull request '前端:修复多分支部署时静态资源的路径问题' (#52) from feature-nxdev into main
Reviewed-on: #52
2026-01-29 22:54:48 +08:00
nanxun a590aa0a26 前端:修复多分支部署时静态资源的路径问题 2026-01-29 22:54:12 +08:00
nanxun 98b5cad586 Merge pull request 'feature-nxdev' (#51) from feature-nxdev into main
Reviewed-on: #51
2026-01-29 22:31:28 +08:00
nanxun 3d9335b97b 前端:修复多分支部署时静态资源的路径问题 2026-01-29 22:30:35 +08:00
nanxun 1df3a0f30e 1111 2026-01-28 18:27:13 +08:00
nanxun e65eeb5ff7 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2026-01-28 18:27:08 +08:00
nanxun e05497a478 1111 2026-01-28 18:26:19 +08:00
nanxun ae99d3290f 后端:
1、事件订阅增加rabbitmq中间件
移动端:
1、完善移动端目录结构和代码框架
2026-01-28 18:19:45 +08:00
nanxun 6cab42679c Merge pull request '前端:修复一堆BUG' (#50) from feature-nxdev into main
Reviewed-on: #50
2026-01-24 14:39:52 +08:00
nanxun f9cc69698c Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM 2026-01-24 14:37:53 +08:00
nanxun f41e7137fe 前端:修复一堆BUG 2026-01-23 17:22:07 +08:00
nanxun 9d942b38c7 Merge pull request 'feature-nxdev' (#49) from feature-nxdev into main
Reviewed-on: #49
2026-01-22 15:29:17 +08:00
nanxun 435db45a52 更新 2026-01-22 15:28:42 +08:00
nanxun a31735dbe2 前端:
修复了已知问题
后端:
修复了已知问题
2026-01-22 15:25:54 +08:00
nanxun 11840a2f30 Merge pull request '前端:' (#48) from feature-nxdev into main
Reviewed-on: #48
2026-01-21 22:32:37 +08:00
nanxun f7e68555b1 前端:
新增添加好友功能
修复给联系人发消息时跳转空会话的情况
修复了已知问题
后端:
修复了已知问题
2026-01-21 22:30:51 +08:00
nanxun cca84f88c0 Merge pull request '前端:' (#47) from feature-nxdev into main
Reviewed-on: #47
2026-01-21 18:50:42 +08:00
nanxun 77744015de 前端:
1、更新界面图标
2、修改消息缓存逻辑
3、新增清空未读消息数
后端:
1、修复不同时区导致的时间显示问题
2、新增清空未读消息数接口
2026-01-21 18:48:05 +08:00
nanxun e18fb9db48 Merge pull request '前端:' (#46) from feature-nxdev into main
Reviewed-on: #46
2026-01-20 23:11:48 +08:00
nanxun bedcf97c9d 前端:
增加会话缓存
后端:
增加触发消息创建事件
增加消息创建事件触发后的处理函数
2026-01-20 23:09:46 +08:00
nanxun df044e23f1 Merge pull request '前端&后端:' (#45) from feature-nxdev into main
Reviewed-on: #45
2026-01-20 20:26:34 +08:00
nanxun be621e9ae2 前端&后端:
互发消息流程打通
2026-01-20 20:25:08 +08:00
nanxun 204853beb9 Merge pull request '前端:' (#44) from feature-nxdev into main
Reviewed-on: #44
2026-01-19 19:39:00 +08:00
nanxun 507788f6a3 前端:
引用signalr三方包
2026-01-19 19:37:47 +08:00
nanxun ac54af3ff8 Merge pull request '前端:' (#43) from feature-nxdev into main
Reviewed-on: #43
2026-01-18 22:38:39 +08:00
nanxun e7dbb651a2 前端:
1、会话列表、消息界面展示与后端打通
后端:
1、修复会话和消息服务现存问题
2、会话对象不再返回Message对象,而是使用MessageBaseDto替代
3、修改查询会话列表和会话信息的逻辑
4、新增消息列表查询
文档:
后端代码规范文档新增从数据库同步到模型的命令
2026-01-18 22:32:55 +08:00
nanxun f565e9d8a3 Merge pull request 'feature-nxdev' (#42) from feature-nxdev into main
Reviewed-on: #42
2026-01-13 22:08:35 +08:00
nanxun d855c8f8fb 前端:完善无感刷新
后端:修复刷新token接口异常报错
2026-01-13 22:07:00 +08:00
nanxun 3b5e8b8eba Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2026-01-13 21:01:21 +08:00
nanxun 3b126f17fc Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2026-01-13 21:01:13 +08:00
nanxun 448cf25538 Merge pull request 'feature-nxdev' (#41) from feature-nxdev into main
Reviewed-on: #41
2026-01-13 20:15:59 +08:00
nanxun 2b2d018658 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2026-01-13 20:14:31 +08:00
nanxun 816b01f13a Merge pull request 'main' (#40) from main into feature-nxdev
Reviewed-on: #40
2026-01-13 20:14:27 +08:00
nanxun da401a20c0 Merge pull request 'main' (#40) from main into feature-nxdev
Reviewed-on: #40
2026-01-13 19:56:57 +08:00
nanxun 9e826153a4 Merge pull request '修改侧栏,添加联系人跳转消息' (#39) from y1 into main
Reviewed-on: #39
2026-01-12 17:40:56 +08:00
chuyang ca4e2b7dd5 修改侧栏,添加联系人跳转消息 2026-01-12 16:44:32 +08:00
nanxun 57deb5c9bf Merge pull request 'feature-nxdev' (#38) from feature-nxdev into main
Reviewed-on: #38
2026-01-03 22:14:44 +08:00
nanxun 5323c29549 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2026-01-03 22:13:38 +08:00
nanxun 41963dde70 add(conversation):完善私聊好友会话服务 2026-01-03 22:13:28 +08:00
nanxun 7d5699ad53 Merge pull request 'feature-nxdev' (#37) from feature-nxdev into main
Reviewed-on: #37
2025-12-30 19:19:35 +08:00
nanxun 5274f0d22d Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 19:19:08 +08:00
nanxun e981ee190f Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 19:18:58 +08:00
nanxun 85144dbf6f Merge pull request 'feature-nxdev' (#36) from feature-nxdev into main
Reviewed-on: #36
2025-12-30 17:59:50 +08:00
nanxun f42e990c45 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 17:59:02 +08:00
nanxun 705c96a230 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 17:58:59 +08:00
nanxun 885b4012ed Merge pull request 'feature-nxdev' (#35) from feature-nxdev into main
Reviewed-on: #35
2025-12-30 17:22:51 +08:00
nanxun 2565678b22 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 17:21:20 +08:00
nanxun f89007f52b Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-30 17:21:11 +08:00
nanxun 36f442a1c2 Merge pull request 'feature-nxdev' (#34) from feature-nxdev into main
Reviewed-on: #34
2025-12-29 22:03:13 +08:00
nanxun 0ea560694b Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-29 22:02:58 +08:00
nanxun c764f0eb03 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-29 22:02:55 +08:00
nanxun 600272486b Merge pull request 'feature-nxdev' (#33) from feature-nxdev into main
Reviewed-on: #33
2025-12-29 21:57:59 +08:00
nanxun 0a440d2914 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-29 21:57:40 +08:00
nanxun d641cb3275 Merge pull request 'main' (#32) from main into feature-nxdev
Reviewed-on: #32
2025-12-29 21:57:33 +08:00
nanxun 26a13c4165 Merge pull request 'main' (#32) from main into feature-nxdev
Reviewed-on: #32
2025-12-29 20:46:10 +08:00
nanxun 6dd93529c2 Merge pull request 'feat/auth-ui-refinement' (#31) from feat/auth-ui-refinement into main
Reviewed-on: #31
2025-12-29 20:45:30 +08:00
nanxun 86841867f3 Merge pull request 'feature-nxdev' (#30) from feature-nxdev into main
Reviewed-on: #30
2025-12-29 20:41:46 +08:00
nanxun 191c05461a Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-29 20:41:19 +08:00
nanxun cf09984056 add(conversationSerivice):后端新增消息会话服务add(main):完善主面板 2025-12-29 20:39:29 +08:00
4yanzhen a40b768f3b style: update App.vue and IconInput.vue for consistent auth UI 2025-12-29 20:37:14 +08:00
4yanzhen ad0ab87747 style: refine login and register pages with green/blue themes and pill-shaped buttons 2025-12-29 20:32:40 +08:00
nanxun afe2c38f74 Merge pull request 'add(main):完善主面板' (#29) from feature-nxdev into main
Reviewed-on: #29
2025-12-29 16:16:03 +08:00
nanxun 964b129e5e add(main):完善主面板 2025-12-29 16:14:50 +08:00
nanxun 4cc1d20b7f Merge pull request 'feature-nxdev' (#28) from feature-nxdev into main
Reviewed-on: #28
2025-12-28 22:14:18 +08:00
nanxun 06410a75e7 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-28 22:13:39 +08:00
nanxun 6c564ceba3 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-28 22:13:35 +08:00
nanxun 9c108de175 Merge pull request 'feature-nxdev' (#27) from feature-nxdev into main
Reviewed-on: #27
2025-12-28 22:02:09 +08:00
nanxun ceae1a0cd8 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-28 22:01:27 +08:00
nanxun 62642ee86a modify(login): 修改登录返回对象,新增返回用户信息 2025-12-28 22:01:24 +08:00
nanxun f8fd54b953 Merge pull request 'add(filter): 新增全局错误拦截器' (#26) from feature-nxdev into main
Reviewed-on: #26
2025-12-15 17:32:04 +08:00
nanxun 3725af7952 add(filter): 新增全局错误拦截器 2025-12-15 17:30:58 +08:00
nanxun 465431ee98 Merge pull request 'feature-nxdev' (#25) from feature-nxdev into main
Reviewed-on: #25
2025-12-14 21:41:32 +08:00
nanxun 82652efed7 add(login):完善登录逻辑 2025-12-14 21:40:34 +08:00
nanxun 6ca7a8275c add(MyButton): 新增按钮组件 2025-12-14 14:12:47 +08:00
nanxun d55fc351ad Merge pull request 'add(components): 添加MyInput组件' (#24) from feature-nxdev into main
Reviewed-on: #24
2025-12-12 22:43:59 +08:00
nanxun fe4a9173a4 add(components): 添加MyInput组件 2025-12-12 22:42:47 +08:00
nanxun 9ea6b1d50e Merge pull request 'feature-nxdev' (#23) from feature-nxdev into main
Reviewed-on: #23
2025-12-07 15:45:31 +08:00
nanxun d90366e304 Merge branch 'feature-nxdev' of https://gitea.nxsir.cn/code/IM into feature-nxdev 2025-12-07 15:45:00 +08:00
nanxun cbff80ba80 add(test):添加单元测试 2025-12-07 15:44:51 +08:00
nanxun 8203dbe6ec Merge pull request 'feature-nxdev' (#22) from feature-nxdev into main
Reviewed-on: #22
2025-12-06 19:59:37 +08:00
nanxun 1d633c3909 Merge pull request 'feature-nxdev' (#21) from feature-nxdev into main
Reviewed-on: #21
2025-12-01 20:47:51 +08:00
nanxun 4c7bdbe9fc Merge pull request 'feature-nxdev' (#20) from feature-nxdev into main
Reviewed-on: #20
2025-11-27 16:56:37 +08:00
nanxun 7c0cf6941f Merge pull request 'feature-nxdev' (#19) from feature-nxdev into main
Reviewed-on: #19
2025-11-16 16:21:49 +08:00
nanxun 0675e1807b Merge pull request 'feature-nxdev' (#18) from feature-nxdev into main
Reviewed-on: #18
2025-11-16 10:56:41 +08:00
nanxun 1807c6905f Merge pull request 'feature-nxdev' (#17) from feature-nxdev into main
Reviewed-on: #17
2025-11-03 15:09:09 +08:00
351 changed files with 23644 additions and 1397 deletions
+29
View File
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.22" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IM_API\IM_API.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>
+205
View File
@@ -0,0 +1,205 @@
using Xunit;
using Microsoft.EntityFrameworkCore;
using Moq;
using AutoMapper;
using IM_API.Services;
using IM_API.Models;
using IM_API.Dtos;
using IM_API.Exceptions;
using IM_API.Tools;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
public class AuthServiceTests
{
// ---------- 辅助:创建独立的 InMemory DbContext ----------
private ImContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<ImContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var context = new ImContext(options);
return context;
}
// ---------- 可用于模拟 SaveChanges 抛异常的 DbContext ----------
private class ThrowOnSaveImContext : ImContext
{
private readonly bool _throw;
public ThrowOnSaveImContext(DbContextOptions<ImContext> options, bool throwOnSave) : base(options)
{
_throw = throwOnSave;
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
if (_throw) throw new InvalidOperationException("Simulated DB save error");
return base.SaveChangesAsync(cancellationToken);
}
}
// ---------- 默认 AutoMapper(简易映射配置) ----------
private IMapper CreateMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<RegisterRequestDto, User>();
cfg.CreateMap<User, UserInfoDto>();
});
return config.CreateMapper();
}
// ---------- 创建 Service(允许注入自定义 mapper ----------
private AuthService CreateService(ImContext context, IMapper mapper = null)
{
var loggerMock = new Mock<ILogger<AuthService>>();
var mapperToUse = mapper ?? CreateMapper();
return new AuthService(context, loggerMock.Object, mapperToUse);
}
// --------------------- 测试用例 ---------------------
[Fact]
public async Task LoginAsync_ShouldReturnUser_WhenValidCredentials()
{
var context = CreateDbContext();
context.Users.Add(new User { Id = 1, Username = "Tom", Password = "123456", NickName = "测试用户" });
await context.SaveChangesAsync();
var service = CreateService(context);
var result = await service.LoginAsync(new LoginRequestDto
{
Username = "Tom",
Password = "123456"
});
Assert.NotNull(result);
Assert.Equal(1, result.Id);
Assert.Equal("Tom", result.Username);
}
[Fact]
public async Task LoginAsync_ShouldThrowException_WhenInvalidCredentials()
{
var context = CreateDbContext();
var service = CreateService(context);
await Assert.ThrowsAsync<BaseException>(async () =>
{
await service.LoginAsync(new LoginRequestDto
{
Username = "NotExist",
Password = "wrong"
});
});
}
[Fact]
public async Task LoginAsync_IsCaseSensitive_ForUsername()
{
// 说明:当前实现是精确匹配 => 区分大小写
var context = CreateDbContext();
context.Users.Add(new User { Id = 10, Username = "Tom", Password = "p" });
await context.SaveChangesAsync();
var service = CreateService(context);
// 使用小写用户名应失败(表明当前逻辑区分大小写)
await Assert.ThrowsAsync<BaseException>(async () =>
{
await service.LoginAsync(new LoginRequestDto { Username = "tom", Password = "p" });
});
}
[Fact]
public async Task RegisterAsync_ShouldPersistUserAndReturnDto_WhenNewUser()
{
var context = CreateDbContext();
var service = CreateService(context);
var request = new RegisterRequestDto
{
Username = "Jerry",
Password = "123456"
};
var result = await service.RegisterAsync(request);
// 返回值正确
Assert.NotNull(result);
Assert.Equal("Jerry", result.Username);
Assert.True(result.Id > 0);
// DB 中确实存在该用户
var persisted = await context.Users.FirstOrDefaultAsync(u => u.Username == "Jerry");
Assert.NotNull(persisted);
Assert.Equal("Jerry", persisted.Username);
}
[Fact]
public async Task RegisterAsync_ShouldThrowException_WhenUserExists()
{
var context = CreateDbContext();
context.Users.Add(new User { Username = "Tom", Password = "12223" });
await context.SaveChangesAsync();
var service = CreateService(context);
var request = new RegisterRequestDto { Username = "Tom", Password = "123" };
await Assert.ThrowsAsync<BaseException>(() => service.RegisterAsync(request));
}
[Fact]
public async Task RegisterAsync_ShouldThrow_WhenMapperThrows()
{
var context = CreateDbContext();
var mapperMock = new Mock<IMapper>();
mapperMock.Setup(m => m.Map<User>(It.IsAny<RegisterRequestDto>()))
.Throws(new Exception("mapper failure"));
var service = CreateService(context, mapperMock.Object);
var req = new RegisterRequestDto { Username = "A", Password = "B" };
var ex = await Assert.ThrowsAsync<Exception>(() => service.RegisterAsync(req));
Assert.Contains("mapper failure", ex.Message);
}
[Fact]
public async Task RegisterAsync_ShouldPropagateSaveException_WhenSaveFails()
{
var options = new DbContextOptionsBuilder<ImContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
// 使用自定义上下文在 SaveChanges 时抛异常
var throwingContext = new ThrowOnSaveImContext(options, throwOnSave: true);
var service = CreateService(throwingContext);
var req = new RegisterRequestDto { Username = "X", Password = "P" };
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.RegisterAsync(req));
Assert.Contains("Simulated DB save error", ex.Message);
}
[Fact]
public async Task RegisterAsync_NullDto_ThrowsNullReferenceException_CurrentBehavior()
{
// 说明:当前实现没有对 dto 为 null 做检查,会触发 NullReferenceException。
// 建议:在生产代码中改为抛 ArgumentNullException 或者进行参数校验并返回明确错误。
var context = CreateDbContext();
var service = CreateService(context);
await Assert.ThrowsAsync<NullReferenceException>(async () =>
{
await service.RegisterAsync(null);
});
}
}
+158
View File
@@ -0,0 +1,158 @@
using Xunit;
using Microsoft.EntityFrameworkCore;
using Moq;
using AutoMapper;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using IM_API.Services;
using IM_API.Models;
using IM_API.Dtos;
using IM_API.Exceptions;
using IM_API.Tools;
public class FriendServiceTests
{
private ImContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<ImContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ImContext(options);
}
private IMapper CreateMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Friend, FriendInfoDto>();
cfg.CreateMap<FriendRequestDto, FriendRequest>();
cfg.CreateMap<HandleFriendRequestDto, FriendRequest>();
cfg.CreateMap<FriendRequest, Friend>()
.ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.ResponseUser))
.ForMember(dest => dest.FriendId, opt => opt.MapFrom(src => src.RequestUser))
.ForMember(dest => dest.RemarkName, opt => opt.MapFrom(src => "AutoAdded"));
});
return config.CreateMapper();
}
private FriendService CreateService(ImContext context)
{
var logger = new Mock<ILogger<FriendService>>();
return new FriendService(context, logger.Object, CreateMapper());
}
// --------------------------- 测试 BlockFriendAsync ---------------------------
[Fact]
public async Task BlockFriendAsync_Should_Set_Status_To_Blocked()
{
var context = CreateDbContext();
context.Friends.Add(new Friend
{
Id = 1,
UserId = 10,
FriendId = 20,
StatusEnum = FriendStatus.Added,
RemarkName = "test remark"
});
await context.SaveChangesAsync();
var service = CreateService(context);
var result = await service.BlockeFriendAsync(1);
Assert.True(result);
Assert.Equal(FriendStatus.Blocked, context.Friends.Find(1).StatusEnum);
}
[Fact]
public async Task BlockFriendAsync_Should_Throw_When_NotFound()
{
var service = CreateService(CreateDbContext());
await Assert.ThrowsAsync<BaseException>(() => service.BlockeFriendAsync(99));
}
// --------------------------- 删除好友关系 ---------------------------
[Fact]
public async Task DeleteFriendAsync_Should_Remove_Friend()
{
var context = CreateDbContext();
context.Friends.Add(new Friend
{
Id = 2,
UserId = 1,
FriendId = 3,
RemarkName = "remark",
StatusEnum = FriendStatus.Added
});
await context.SaveChangesAsync();
var service = CreateService(context);
var result = await service.DeleteFriendAsync(2);
Assert.True(result);
Assert.Empty(context.Friends);
}
// --------------------------- 获取好友列表 ---------------------------
[Fact]
public async Task GetFriendListAsync_Should_Return_Only_Added_Friends()
{
var context = CreateDbContext();
context.Friends.AddRange(new List<Friend>
{
new Friend{ UserId = 1, FriendId = 2, RemarkName ="a1", StatusEnum = FriendStatus.Added },
new Friend{ UserId = 1, FriendId = 3, RemarkName ="a2", StatusEnum = FriendStatus.Blocked }
});
await context.SaveChangesAsync();
var service = CreateService(context);
var result = await service.GetFriendListAsync(1, 1, 10, false);
Assert.Single(result);
}
// --------------------------- 发起好友请求 ---------------------------
[Fact]
public async Task SendFriendRequestAsync_Should_Succeed()
{
var context = CreateDbContext();
context.Users.Add(new User { Id = 10, Username = "A", Password = "123" });
context.Users.Add(new User { Id = 20, Username = "B", Password = "123" });
await context.SaveChangesAsync();
var service = CreateService(context);
var result = await service.SendFriendRequestAsync(new FriendRequestDto
{
FromUserId = 10,
ToUserId = 20
});
Assert.True(result);
Assert.Single(context.FriendRequests);
Assert.Single(context.Friends);
}
[Fact]
public async Task SendFriendRequestAsync_Should_Throw_When_User_NotFound()
{
var context = CreateDbContext();
context.Users.Add(new User { Id = 10, Username = "A", Password = "123" });
await context.SaveChangesAsync();
var service = CreateService(context);
await Assert.ThrowsAsync<BaseException>(() => service.SendFriendRequestAsync(new FriendRequestDto
{
FromUserId = 10,
ToUserId = 99
}));
}
}
+182
View File
@@ -0,0 +1,182 @@
using Xunit;
using Microsoft.EntityFrameworkCore;
using Moq;
using AutoMapper;
using IM_API.Services;
using IM_API.Models;
using IM_API.Dtos;
using IM_API.Exceptions;
using IM_API.Tools;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
public class UserServiceTests
{
private ImContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<ImContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ImContext(options);
}
private IMapper CreateMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<User, UserInfoDto>();
cfg.CreateMap<UpdateUserDto, User>();
});
return config.CreateMapper();
}
private UserService CreateService(ImContext context)
{
var loggerMock = new Mock<ILogger<UserService>>();
var mapper = CreateMapper();
return new UserService(context, loggerMock.Object, mapper);
}
// ========== GetUserInfoAsync ==========
[Fact]
public async Task GetUserInfoAsync_ShouldReturnUser_WhenExists()
{
var context = CreateDbContext();
context.Users.Add(new User { Id = 1, Username = "Tom", Password = "123456" });
await context.SaveChangesAsync();
var service = CreateService(context);
var result = await service.GetUserInfoAsync(1);
Assert.NotNull(result);
Assert.Equal("Tom", result.Username);
}
[Fact]
public async Task GetUserInfoAsync_ShouldThrow_WhenNotFound()
{
var service = CreateService(CreateDbContext());
await Assert.ThrowsAsync<BaseException>(() =>
service.GetUserInfoAsync(999)
);
}
// ========== GetUserInfoByUsernameAsync ==========
[Fact]
public async Task GetUserInfoByUsernameAsync_ShouldReturn_WhenExists()
{
var context = CreateDbContext();
context.Users.Add(new User { Id = 2, Username = "Jerry", Password = "aaa" });
await context.SaveChangesAsync();
var service = CreateService(context);
var result = await service.GetUserInfoByUsernameAsync("Jerry");
Assert.NotNull(result);
Assert.Equal(2, result.Id);
}
[Fact]
public async Task GetUserInfoByUsernameAsync_ShouldThrow_WhenNotFound()
{
var service = CreateService(CreateDbContext());
await Assert.ThrowsAsync<BaseException>(() =>
service.GetUserInfoByUsernameAsync("Nobody")
);
}
// ========== ResetPasswordAsync ==========
[Fact]
public async Task ResetPasswordAsync_ShouldReset_WhenCorrectPassword()
{
var context = CreateDbContext();
context.Users.Add(new User { Id = 3, Username = "test", Password = "old" });
await context.SaveChangesAsync();
var service = CreateService(context);
var result = await service.ResetPasswordAsync(3, "old", "new");
Assert.True(result);
Assert.Equal("new", context.Users.Find(3).Password);
}
[Fact]
public async Task ResetPasswordAsync_ShouldThrow_WhenWrongPassword()
{
var context = CreateDbContext();
context.Users.Add(new User { Id = 4, Username = "test", Password = "oldPwd" });
await context.SaveChangesAsync();
var service = CreateService(context);
await Assert.ThrowsAsync<BaseException>(() =>
service.ResetPasswordAsync(4, "wrong", "new")
);
}
[Fact]
public async Task ResetPasswordAsync_ShouldThrow_WhenUserNotFound()
{
var service = CreateService(CreateDbContext());
await Assert.ThrowsAsync<BaseException>(() =>
service.ResetPasswordAsync(123, "anything", "new")
);
}
// ========== UpdateOlineStatusAsync ==========
[Fact]
public async Task UpdateOlineStatusAsync_ShouldUpdateStatus()
{
var context = CreateDbContext();
context.Users.Add(new User { Id = 5, Username = "test", Password = "p", OnlineStatusEnum = UserOnlineStatus.Offline });
await context.SaveChangesAsync();
var service = CreateService(context);
var result = await service.UpdateOlineStatusAsync(5, UserOnlineStatus.Online);
Assert.True(result);
Assert.Equal(UserOnlineStatus.Online, context.Users.Find(5).OnlineStatusEnum);
}
[Fact]
public async Task UpdateOlineStatusAsync_ShouldThrow_WhenUserNotFound()
{
var service = CreateService(CreateDbContext());
await Assert.ThrowsAsync<BaseException>(() =>
service.UpdateOlineStatusAsync(999, UserOnlineStatus.Online)
);
}
// ========== UpdateUserAsync ==========
[Fact]
public async Task UpdateUserAsync_ShouldUpdateUserFields()
{
var context = CreateDbContext();
context.Users.Add(new User { Id = 6, Username = "test", Password = "p", NickName = "OldName" });
await context.SaveChangesAsync();
var service = CreateService(context);
var dto = new UpdateUserDto { NickName = "NewName" };
var result = await service.UpdateUserAsync(6, dto);
Assert.NotNull(result);
Assert.Equal("NewName", context.Users.Find(6).NickName);
}
[Fact]
public async Task UpdateUserAsync_ShouldThrow_WhenNotFound()
{
var service = CreateService(CreateDbContext());
await Assert.ThrowsAsync<BaseException>(() =>
service.UpdateUserAsync(123, new UpdateUserDto { NickName = "Test" })
);
}
}
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,19 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "8.0.0"
}
],
"configProperties": {
"System.Reflection.NullabilityInfoContext.IsSupported": true,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,20 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "8.0.0"
}
],
"configProperties": {
"System.GC.Server": true,
"System.Reflection.NullabilityInfoContext.IsSupported": true,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}
@@ -0,0 +1 @@
{"Version":1,"ManifestType":"Build","Endpoints":[]}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
@@ -0,0 +1,20 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Jwt": {
"Key": "YourSuperSecretKey123456784124214190!",
"Issuer": "IMDemo",
"Audience": "IMClients",
"AccessTokenMinutes": 30,
"RefreshTokenDays": 30
},
"ConnectionStrings": {
"DefaultConnection": "Server=frp-era.com;Port=26582;Database=IM;User=product;Password=12345678;",
"Redis": "192.168.5.100:6379"
}
}

Some files were not shown because too many files have changed in this diff Show More