Expand Ikar messaging and Android release support
This commit is contained in:
@@ -0,0 +1,790 @@
|
||||
using Ikar.Server.Controllers;
|
||||
using Ikar.Server.Data;
|
||||
using Ikar.Server.Data.Entities;
|
||||
using Ikar.Server.Infrastructure;
|
||||
using Ikar.Server.Infrastructure.Auth;
|
||||
using Ikar.Server.Infrastructure.Bots;
|
||||
using Ikar.Server.Infrastructure.Hubs;
|
||||
using Ikar.Server.Infrastructure.Push;
|
||||
using Ikar.Server.Infrastructure.Storage;
|
||||
using Ikar.Shared;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Ikar.Server.Tests;
|
||||
|
||||
public sealed class BotApiControllerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task SendDocument_DecodesPercentEncodedFileName()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
try
|
||||
{
|
||||
await using var database = new SqliteTestDatabase();
|
||||
await using var dbContext = await database.CreateContextAsync();
|
||||
|
||||
var owner = CreateUser("owner");
|
||||
var botUser = CreateUser("documentbot", isBot: true);
|
||||
var tokenService = new BotTokenService();
|
||||
var token = "bot-token";
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
OwnerUserId = owner.Id,
|
||||
OwnerUser = owner,
|
||||
TokenHash = tokenService.HashToken(token)
|
||||
};
|
||||
var chat = CreateDirectChat(owner, botUser);
|
||||
|
||||
dbContext.AddRange(owner, botUser, bot, chat);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
await using var stream = new MemoryStream("%PDF-1.3"u8.ToArray());
|
||||
var encodedFileName = "Fluke_8508A_" + Uri.EscapeDataString("методика.pdf");
|
||||
var file = new FormFile(stream, 0, stream.Length, "file", encodedFileName)
|
||||
{
|
||||
Headers = new HeaderDictionary(),
|
||||
ContentType = "application/pdf"
|
||||
};
|
||||
var controller = CreateController(dbContext, tokenService, tempRoot);
|
||||
|
||||
var response = await controller.SendDocument(
|
||||
token,
|
||||
new BotSendAttachmentRequest
|
||||
{
|
||||
ChatId = chat.Id,
|
||||
Text = "",
|
||||
File = file
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
var message = ControllerTestFixture.ValueOf(response);
|
||||
var attachment = Assert.Single(message.Attachments);
|
||||
Assert.Equal("Fluke_8508A_методика.pdf", attachment.FileName);
|
||||
Assert.Equal("application/pdf", attachment.ContentType);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendDocument_SavesOggAsVoiceNoteAttachment()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
try
|
||||
{
|
||||
await using var database = new SqliteTestDatabase();
|
||||
await using var dbContext = await database.CreateContextAsync();
|
||||
|
||||
var owner = CreateUser("owner");
|
||||
var botUser = CreateUser("documentbot", isBot: true);
|
||||
var tokenService = new BotTokenService();
|
||||
var token = "bot-token";
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
OwnerUserId = owner.Id,
|
||||
OwnerUser = owner,
|
||||
TokenHash = tokenService.HashToken(token)
|
||||
};
|
||||
var chat = CreateDirectChat(owner, botUser);
|
||||
|
||||
dbContext.AddRange(owner, botUser, bot, chat);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
await using var stream = new MemoryStream([1, 2, 3, 4]);
|
||||
var file = new FormFile(stream, 0, stream.Length, "file", "song.ogg")
|
||||
{
|
||||
Headers = new HeaderDictionary(),
|
||||
ContentType = "audio/ogg"
|
||||
};
|
||||
var controller = CreateController(dbContext, tokenService, tempRoot);
|
||||
|
||||
var response = await controller.SendDocument(
|
||||
token,
|
||||
new BotSendAttachmentRequest
|
||||
{
|
||||
ChatId = chat.Id,
|
||||
Text = "",
|
||||
File = file
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
var message = ControllerTestFixture.ValueOf(response);
|
||||
var attachment = Assert.Single(message.Attachments);
|
||||
Assert.Equal(AttachmentKind.VoiceNote, attachment.Kind);
|
||||
Assert.Equal("audio/ogg", attachment.ContentType);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendPhoto_SavesImageAttachment()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
try
|
||||
{
|
||||
await using var database = new SqliteTestDatabase();
|
||||
await using var dbContext = await database.CreateContextAsync();
|
||||
|
||||
var owner = CreateUser("owner");
|
||||
var botUser = CreateUser("photobot", isBot: true);
|
||||
var tokenService = new BotTokenService();
|
||||
var token = "bot-token";
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
OwnerUserId = owner.Id,
|
||||
OwnerUser = owner,
|
||||
TokenHash = tokenService.HashToken(token)
|
||||
};
|
||||
var chat = CreateDirectChat(owner, botUser);
|
||||
|
||||
dbContext.AddRange(owner, botUser, bot, chat);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
await using var stream = new MemoryStream([0xFF, 0xD8, 0xFF, 0xD9]);
|
||||
var file = new FormFile(stream, 0, stream.Length, "file", "photo.jpg")
|
||||
{
|
||||
Headers = new HeaderDictionary(),
|
||||
ContentType = "image/jpeg"
|
||||
};
|
||||
var controller = CreateController(dbContext, tokenService, tempRoot);
|
||||
|
||||
var response = await controller.SendPhoto(
|
||||
token,
|
||||
new BotSendAttachmentRequest
|
||||
{
|
||||
ChatId = chat.Id,
|
||||
Text = "caption",
|
||||
File = file
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
var message = ControllerTestFixture.ValueOf(response);
|
||||
Assert.Equal("caption", message.Text);
|
||||
var attachment = Assert.Single(message.Attachments);
|
||||
Assert.Equal(AttachmentKind.File, attachment.Kind);
|
||||
Assert.Equal("image/jpeg", attachment.ContentType);
|
||||
Assert.Equal("photo.jpg", attachment.FileName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendPhoto_RejectsVideoAttachment()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
try
|
||||
{
|
||||
await using var database = new SqliteTestDatabase();
|
||||
await using var dbContext = await database.CreateContextAsync();
|
||||
|
||||
var owner = CreateUser("owner");
|
||||
var botUser = CreateUser("photobot", isBot: true);
|
||||
var tokenService = new BotTokenService();
|
||||
var token = "bot-token";
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
OwnerUserId = owner.Id,
|
||||
OwnerUser = owner,
|
||||
TokenHash = tokenService.HashToken(token)
|
||||
};
|
||||
var chat = CreateDirectChat(owner, botUser);
|
||||
|
||||
dbContext.AddRange(owner, botUser, bot, chat);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
await using var stream = new MemoryStream([1, 2, 3, 4]);
|
||||
var file = new FormFile(stream, 0, stream.Length, "file", "clip.mp4")
|
||||
{
|
||||
Headers = new HeaderDictionary(),
|
||||
ContentType = "video/mp4"
|
||||
};
|
||||
var controller = CreateController(dbContext, tokenService, tempRoot);
|
||||
|
||||
var response = await controller.SendPhoto(
|
||||
token,
|
||||
new BotSendAttachmentRequest
|
||||
{
|
||||
ChatId = chat.Id,
|
||||
Text = "",
|
||||
File = file
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
var badRequest = Assert.IsType<Microsoft.AspNetCore.Mvc.BadRequestObjectResult>(response.Result);
|
||||
Assert.Equal("sendPhoto accepts only image files.", badRequest.Value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendVideo_SavesVideoAttachment()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
try
|
||||
{
|
||||
await using var database = new SqliteTestDatabase();
|
||||
await using var dbContext = await database.CreateContextAsync();
|
||||
|
||||
var owner = CreateUser("owner");
|
||||
var botUser = CreateUser("videobot", isBot: true);
|
||||
var tokenService = new BotTokenService();
|
||||
var token = "bot-token";
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
OwnerUserId = owner.Id,
|
||||
OwnerUser = owner,
|
||||
TokenHash = tokenService.HashToken(token)
|
||||
};
|
||||
var chat = CreateDirectChat(owner, botUser);
|
||||
|
||||
dbContext.AddRange(owner, botUser, bot, chat);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
await using var stream = new MemoryStream([0, 0, 0, 24, 102, 116, 121, 112]);
|
||||
var file = new FormFile(stream, 0, stream.Length, "file", "clip.mp4")
|
||||
{
|
||||
Headers = new HeaderDictionary(),
|
||||
ContentType = "video/mp4"
|
||||
};
|
||||
var controller = CreateController(dbContext, tokenService, tempRoot);
|
||||
|
||||
var response = await controller.SendVideo(
|
||||
token,
|
||||
new BotSendAttachmentRequest
|
||||
{
|
||||
ChatId = chat.Id,
|
||||
Text = "clip",
|
||||
File = file
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
var message = ControllerTestFixture.ValueOf(response);
|
||||
Assert.Equal("clip", message.Text);
|
||||
var attachment = Assert.Single(message.Attachments);
|
||||
Assert.Equal(AttachmentKind.File, attachment.Kind);
|
||||
Assert.Equal("video/mp4", attachment.ContentType);
|
||||
Assert.Equal("clip.mp4", attachment.FileName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendMediaGroup_SavesImageAndVideoAlbum()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
try
|
||||
{
|
||||
await using var database = new SqliteTestDatabase();
|
||||
await using var dbContext = await database.CreateContextAsync();
|
||||
|
||||
var owner = CreateUser("owner");
|
||||
var botUser = CreateUser("albumbot", isBot: true);
|
||||
var tokenService = new BotTokenService();
|
||||
var token = "bot-token";
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
OwnerUserId = owner.Id,
|
||||
OwnerUser = owner,
|
||||
TokenHash = tokenService.HashToken(token)
|
||||
};
|
||||
var chat = CreateDirectChat(owner, botUser);
|
||||
|
||||
dbContext.AddRange(owner, botUser, bot, chat);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var photo = CreateFormFile([0xFF, 0xD8, 0xFF, 0xD9], "photo.jpg", "image/jpeg");
|
||||
var video = CreateFormFile([0, 0, 0, 24, 102, 116, 121, 112], "clip.mp4", "video/mp4");
|
||||
var controller = CreateController(dbContext, tokenService, tempRoot);
|
||||
|
||||
var response = await controller.SendMediaGroup(
|
||||
token,
|
||||
new BotSendMediaGroupRequest
|
||||
{
|
||||
ChatId = chat.Id,
|
||||
Text = "album",
|
||||
Files = [photo, video]
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
var message = ControllerTestFixture.ValueOf(response);
|
||||
Assert.Equal("album", message.Text);
|
||||
Assert.NotNull(message.MediaAlbumId);
|
||||
Assert.Equal(2, message.Attachments.Count);
|
||||
Assert.Equal("photo.jpg", message.Attachments[0].FileName);
|
||||
Assert.Equal(0, message.Attachments[0].SortOrder);
|
||||
Assert.Equal("clip.mp4", message.Attachments[1].FileName);
|
||||
Assert.Equal(1, message.Attachments[1].SortOrder);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendMediaGroup_RejectsNonMediaAttachment()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
try
|
||||
{
|
||||
await using var database = new SqliteTestDatabase();
|
||||
await using var dbContext = await database.CreateContextAsync();
|
||||
|
||||
var owner = CreateUser("owner");
|
||||
var botUser = CreateUser("albumbot", isBot: true);
|
||||
var tokenService = new BotTokenService();
|
||||
var token = "bot-token";
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
OwnerUserId = owner.Id,
|
||||
OwnerUser = owner,
|
||||
TokenHash = tokenService.HashToken(token)
|
||||
};
|
||||
var chat = CreateDirectChat(owner, botUser);
|
||||
|
||||
dbContext.AddRange(owner, botUser, bot, chat);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var photo = CreateFormFile([0xFF, 0xD8, 0xFF, 0xD9], "photo.jpg", "image/jpeg");
|
||||
var document = CreateFormFile([0x25, 0x50, 0x44, 0x46], "manual.pdf", "application/pdf");
|
||||
var controller = CreateController(dbContext, tokenService, tempRoot);
|
||||
|
||||
var response = await controller.SendMediaGroup(
|
||||
token,
|
||||
new BotSendMediaGroupRequest
|
||||
{
|
||||
ChatId = chat.Id,
|
||||
Files = [photo, document]
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
var badRequest = Assert.IsType<Microsoft.AspNetCore.Mvc.BadRequestObjectResult>(response.Result);
|
||||
Assert.Equal("sendMediaGroup accepts only image and video files.", badRequest.Value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendVoice_SavesOctetStreamOggNameAsVoiceNoteAttachment()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
try
|
||||
{
|
||||
await using var database = new SqliteTestDatabase();
|
||||
await using var dbContext = await database.CreateContextAsync();
|
||||
|
||||
var owner = CreateUser("owner");
|
||||
var botUser = CreateUser("voicebot", isBot: true);
|
||||
var tokenService = new BotTokenService();
|
||||
var token = "bot-token";
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
OwnerUserId = owner.Id,
|
||||
OwnerUser = owner,
|
||||
TokenHash = tokenService.HashToken(token)
|
||||
};
|
||||
var chat = CreateDirectChat(owner, botUser);
|
||||
|
||||
dbContext.AddRange(owner, botUser, bot, chat);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
await using var stream = new MemoryStream([1, 2, 3, 4, 5]);
|
||||
var file = new FormFile(stream, 0, stream.Length, "file", "Правила ogg")
|
||||
{
|
||||
Headers = new HeaderDictionary(),
|
||||
ContentType = "application/octet-stream"
|
||||
};
|
||||
var controller = CreateController(dbContext, tokenService, tempRoot);
|
||||
|
||||
var response = await controller.SendVoice(
|
||||
token,
|
||||
new BotSendAttachmentRequest
|
||||
{
|
||||
ChatId = chat.Id,
|
||||
Text = "",
|
||||
File = file
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
var message = ControllerTestFixture.ValueOf(response);
|
||||
var attachment = Assert.Single(message.Attachments);
|
||||
Assert.Equal(AttachmentKind.VoiceNote, attachment.Kind);
|
||||
Assert.Equal("audio/ogg", attachment.ContentType);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendMessage_ChannelWithLinkedDiscussion_CreatesDiscussionRoot()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
try
|
||||
{
|
||||
await using var database = new SqliteTestDatabase();
|
||||
await using var dbContext = await database.CreateContextAsync();
|
||||
|
||||
var owner = CreateUser("owner");
|
||||
var botUser = CreateUser("newsbot", isBot: true);
|
||||
var tokenService = new BotTokenService();
|
||||
var token = "bot-token";
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
OwnerUserId = owner.Id,
|
||||
OwnerUser = owner,
|
||||
TokenHash = tokenService.HashToken(token)
|
||||
};
|
||||
var group = CreateGroup(owner, botUser, canBotPost: true);
|
||||
var channel = CreateChannel(owner, botUser, group);
|
||||
|
||||
dbContext.AddRange(owner, botUser, bot, group, channel);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var controller = CreateController(dbContext, tokenService, tempRoot);
|
||||
var response = await controller.SendMessage(
|
||||
token,
|
||||
new BotSendMessageRequest(channel.Id, "Bot channel post"),
|
||||
CancellationToken.None);
|
||||
|
||||
var postDto = ControllerTestFixture.ValueOf(response);
|
||||
Assert.NotNull(postDto.DiscussionMessageId);
|
||||
|
||||
var discussionRoot = dbContext.Messages.Single(x => x.Id == postDto.DiscussionMessageId);
|
||||
Assert.Equal(group.Id, discussionRoot.ChatId);
|
||||
Assert.Equal("Bot channel post", discussionRoot.Text);
|
||||
Assert.Equal(channel.Title, discussionRoot.ForwardedFromDisplayName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendVoice_ChannelWithLinkedDiscussion_MirrorsVoiceAttachmentToDiscussionRoot()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
try
|
||||
{
|
||||
await using var database = new SqliteTestDatabase();
|
||||
await using var dbContext = await database.CreateContextAsync();
|
||||
|
||||
var owner = CreateUser("owner");
|
||||
var botUser = CreateUser("voicebot", isBot: true);
|
||||
var tokenService = new BotTokenService();
|
||||
var token = "bot-token";
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
OwnerUserId = owner.Id,
|
||||
OwnerUser = owner,
|
||||
TokenHash = tokenService.HashToken(token)
|
||||
};
|
||||
var group = CreateGroup(owner, botUser, canBotPost: true);
|
||||
var channel = CreateChannel(owner, botUser, group);
|
||||
|
||||
dbContext.AddRange(owner, botUser, bot, group, channel);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var file = CreateFormFile([1, 2, 3, 4], "rules.ogg", "application/octet-stream");
|
||||
var controller = CreateController(dbContext, tokenService, tempRoot);
|
||||
var response = await controller.SendVoice(
|
||||
token,
|
||||
new BotSendAttachmentRequest
|
||||
{
|
||||
ChatId = channel.Id,
|
||||
Text = "Rules",
|
||||
File = file
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
||||
var postDto = ControllerTestFixture.ValueOf(response);
|
||||
Assert.NotNull(postDto.DiscussionMessageId);
|
||||
|
||||
var discussionRoot = dbContext.Messages
|
||||
.Include(x => x.Attachments)
|
||||
.Single(x => x.Id == postDto.DiscussionMessageId);
|
||||
var mirroredAttachment = Assert.Single(discussionRoot.Attachments);
|
||||
Assert.Equal(group.Id, discussionRoot.ChatId);
|
||||
Assert.Equal("Rules", discussionRoot.Text);
|
||||
Assert.Equal(AttachmentKind.VoiceNote, mirroredAttachment.Kind);
|
||||
Assert.Equal("audio/ogg", mirroredAttachment.ContentType);
|
||||
Assert.Equal("rules.ogg", mirroredAttachment.OriginalFileName);
|
||||
Assert.True(File.Exists(mirroredAttachment.StoragePath));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendMessage_DeniesRestrictedGroupBot()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
try
|
||||
{
|
||||
await using var database = new SqliteTestDatabase();
|
||||
await using var dbContext = await database.CreateContextAsync();
|
||||
|
||||
var owner = CreateUser("owner");
|
||||
var botUser = CreateUser("restrictedbot", isBot: true);
|
||||
var tokenService = new BotTokenService();
|
||||
var token = "bot-token";
|
||||
var bot = new Bot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
OwnerUserId = owner.Id,
|
||||
OwnerUser = owner,
|
||||
TokenHash = tokenService.HashToken(token)
|
||||
};
|
||||
var group = CreateGroup(owner, botUser, canBotPost: false);
|
||||
|
||||
dbContext.AddRange(owner, botUser, bot, group);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
var controller = CreateController(dbContext, tokenService, tempRoot);
|
||||
|
||||
var response = await controller.SendMessage(
|
||||
token,
|
||||
new BotSendMessageRequest(group.Id, "test"),
|
||||
CancellationToken.None);
|
||||
|
||||
var forbidden = Assert.IsType<Microsoft.AspNetCore.Mvc.ObjectResult>(response.Result);
|
||||
Assert.Equal(StatusCodes.Status403Forbidden, forbidden.StatusCode);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BotApiController CreateController(
|
||||
IkarDbContext dbContext,
|
||||
BotTokenService tokenService,
|
||||
string contentRootPath) =>
|
||||
new(
|
||||
dbContext,
|
||||
new PresenceTracker(),
|
||||
tokenService,
|
||||
new BotUpdateService(dbContext),
|
||||
new NoopHubContext<MessengerHub>(),
|
||||
new PushDispatchQueue(),
|
||||
new AttachmentStorageService(new TestWebHostEnvironment(contentRootPath)));
|
||||
|
||||
private static FormFile CreateFormFile(byte[] content, string fileName, string contentType)
|
||||
{
|
||||
var stream = new MemoryStream(content);
|
||||
return new FormFile(stream, 0, stream.Length, "file", fileName)
|
||||
{
|
||||
Headers = new HeaderDictionary(),
|
||||
ContentType = contentType
|
||||
};
|
||||
}
|
||||
|
||||
private static User CreateUser(string username, bool isBot = false) =>
|
||||
new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Username = username,
|
||||
NormalizedUsername = username.ToUpperInvariant(),
|
||||
DisplayName = username,
|
||||
PasswordHash = "test",
|
||||
IsBot = isBot
|
||||
};
|
||||
|
||||
private static Chat CreateChannel(User owner, User botUser, Chat linkedDiscussionChat)
|
||||
{
|
||||
var channel = new Chat
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = ChatType.Channel,
|
||||
Title = "News",
|
||||
CreatedById = owner.Id,
|
||||
CreatedBy = owner,
|
||||
LinkedDiscussionChatId = linkedDiscussionChat.Id,
|
||||
CreatedAt = new DateTimeOffset(2026, 5, 21, 12, 0, 0, TimeSpan.Zero)
|
||||
};
|
||||
channel.Members.Add(new ChatMember
|
||||
{
|
||||
ChatId = channel.Id,
|
||||
Chat = channel,
|
||||
UserId = owner.Id,
|
||||
User = owner,
|
||||
IsOwner = true,
|
||||
Role = ChatMemberRole.Owner,
|
||||
CanPostMessages = true,
|
||||
CanDeleteMessages = true,
|
||||
CanInviteUsers = true,
|
||||
CanPinMessages = true
|
||||
});
|
||||
channel.Members.Add(new ChatMember
|
||||
{
|
||||
ChatId = channel.Id,
|
||||
Chat = channel,
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
Role = ChatMemberRole.Admin,
|
||||
CanPostMessages = true
|
||||
});
|
||||
return channel;
|
||||
}
|
||||
|
||||
private static Chat CreateDirectChat(User first, User second)
|
||||
{
|
||||
var chat = new Chat
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = ChatType.Direct,
|
||||
CreatedById = first.Id,
|
||||
CreatedBy = first,
|
||||
CreatedAt = new DateTimeOffset(2026, 5, 21, 12, 0, 0, TimeSpan.Zero)
|
||||
};
|
||||
chat.Members.Add(new ChatMember { ChatId = chat.Id, Chat = chat, UserId = first.Id, User = first });
|
||||
chat.Members.Add(new ChatMember { ChatId = chat.Id, Chat = chat, UserId = second.Id, User = second });
|
||||
return chat;
|
||||
}
|
||||
|
||||
private static Chat CreateGroup(User owner, User botUser, bool canBotPost)
|
||||
{
|
||||
var chat = new Chat
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = ChatType.Group,
|
||||
Title = "Group",
|
||||
CreatedById = owner.Id,
|
||||
CreatedBy = owner,
|
||||
CreatedAt = new DateTimeOffset(2026, 5, 21, 12, 0, 0, TimeSpan.Zero)
|
||||
};
|
||||
chat.Members.Add(new ChatMember
|
||||
{
|
||||
ChatId = chat.Id,
|
||||
Chat = chat,
|
||||
UserId = owner.Id,
|
||||
User = owner,
|
||||
IsOwner = true,
|
||||
Role = ChatMemberRole.Owner,
|
||||
CanPostMessages = true,
|
||||
CanDeleteMessages = true,
|
||||
CanInviteUsers = true,
|
||||
CanPinMessages = true
|
||||
});
|
||||
chat.Members.Add(new ChatMember
|
||||
{
|
||||
ChatId = chat.Id,
|
||||
Chat = chat,
|
||||
UserId = botUser.Id,
|
||||
User = botUser,
|
||||
Role = ChatMemberRole.Member,
|
||||
CanPostMessages = canBotPost
|
||||
});
|
||||
return chat;
|
||||
}
|
||||
|
||||
private sealed class TestWebHostEnvironment(string contentRootPath) : IWebHostEnvironment
|
||||
{
|
||||
public string ApplicationName { get; set; } = "Ikar.Server.Tests";
|
||||
public IFileProvider WebRootFileProvider { get; set; } = new NullFileProvider();
|
||||
public string WebRootPath { get; set; } = contentRootPath;
|
||||
public string EnvironmentName { get; set; } = "Development";
|
||||
public string ContentRootPath { get; set; } = contentRootPath;
|
||||
public IFileProvider ContentRootFileProvider { get; set; } = new PhysicalFileProvider(contentRootPath);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user