Add customer communication system

This commit is contained in:
sysadmin
2025-08-27 18:02:39 +01:00
parent 1f7c0af497
commit eae5be3e7c
136 changed files with 14552 additions and 97 deletions

View File

@@ -0,0 +1,189 @@
using System;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using TeleBot.Services;
using Xunit;
namespace TeleBot.Tests.Services
{
public class PrivacyServiceTests
{
private readonly PrivacyService _privacyService;
private readonly Mock<IConfiguration> _mockConfiguration;
private readonly Mock<ILogger<PrivacyService>> _mockLogger;
public PrivacyServiceTests()
{
_mockConfiguration = new Mock<IConfiguration>();
_mockLogger = new Mock<ILogger<PrivacyService>>();
// Set up default configuration
_mockConfiguration.Setup(x => x["Privacy:HashSalt"])
.Returns("TestSalt123");
_mockConfiguration.Setup(x => x["Privacy:EnableTor"])
.Returns("false");
_privacyService = new PrivacyService(_mockConfiguration.Object, _mockLogger.Object);
}
[Fact]
public void HashIdentifier_ShouldReturnConsistentHash()
{
// Arrange
long telegramId = 123456789;
// Act
var hash1 = _privacyService.HashIdentifier(telegramId);
var hash2 = _privacyService.HashIdentifier(telegramId);
// Assert
hash1.Should().NotBeNullOrEmpty();
hash2.Should().NotBeNullOrEmpty();
hash1.Should().Be(hash2, "Hash should be consistent for the same input");
}
[Fact]
public void HashIdentifier_DifferentIds_ShouldReturnDifferentHashes()
{
// Arrange
long telegramId1 = 123456789;
long telegramId2 = 987654321;
// Act
var hash1 = _privacyService.HashIdentifier(telegramId1);
var hash2 = _privacyService.HashIdentifier(telegramId2);
// Assert
hash1.Should().NotBe(hash2, "Different IDs should produce different hashes");
}
[Fact]
public void GenerateAnonymousReference_ShouldReturnUniqueReferences()
{
// Act
var ref1 = _privacyService.GenerateAnonymousReference();
var ref2 = _privacyService.GenerateAnonymousReference();
// Assert
ref1.Should().StartWith("ANON-");
ref2.Should().StartWith("ANON-");
ref1.Should().HaveLength(17); // ANON- (5) + 12 characters
ref1.Should().NotBe(ref2, "Each reference should be unique");
}
[Fact]
public void GenerateAnonymousReference_ShouldNotContainSpecialCharacters()
{
// Act
var reference = _privacyService.GenerateAnonymousReference();
// Assert
reference.Should().MatchRegex(@"^ANON-[A-Z0-9]+$");
}
[Fact]
public async Task CreateTorHttpClient_WhenTorDisabled_ShouldReturnRegularClient()
{
// Arrange
_mockConfiguration.Setup(x => x["Privacy:EnableTor"])
.Returns("false");
// Act
var client = await _privacyService.CreateTorHttpClient();
// Assert
client.Should().NotBeNull();
client.Timeout.Should().Be(TimeSpan.FromSeconds(100)); // Default timeout
}
[Fact]
public void EncryptData_ShouldEncryptAndDecryptSuccessfully()
{
// Arrange
var originalData = System.Text.Encoding.UTF8.GetBytes("Secret message");
var key = "TestEncryptionKey123";
// Act
var encrypted = _privacyService.EncryptData(originalData, key);
var decrypted = _privacyService.DecryptData(encrypted, key);
// Assert
encrypted.Should().NotBeEquivalentTo(originalData, "Data should be encrypted");
decrypted.Should().BeEquivalentTo(originalData, "Decrypted data should match original");
}
[Fact]
public void EncryptData_WithDifferentKeys_ShouldProduceDifferentResults()
{
// Arrange
var originalData = System.Text.Encoding.UTF8.GetBytes("Secret message");
var key1 = "Key1";
var key2 = "Key2";
// Act
var encrypted1 = _privacyService.EncryptData(originalData, key1);
var encrypted2 = _privacyService.EncryptData(originalData, key2);
// Assert
encrypted1.Should().NotBeEquivalentTo(encrypted2, "Different keys should produce different encryptions");
}
[Fact]
public void SanitizeLogMessage_ShouldRemoveEmail()
{
// Arrange
var message = "User email is test@example.com in the system";
// Act
_privacyService.SanitizeLogMessage(ref message);
// Assert
message.Should().Be("User email is [EMAIL_REDACTED] in the system");
}
[Fact]
public void SanitizeLogMessage_ShouldRemovePhoneNumber()
{
// Arrange
var message = "Contact number: 555-123-4567";
// Act
_privacyService.SanitizeLogMessage(ref message);
// Assert
message.Should().Be("Contact number: [PHONE_REDACTED]");
}
[Fact]
public void SanitizeLogMessage_ShouldRemoveTelegramId()
{
// Arrange
var message = "Processing request for telegram_id:123456789";
// Act
_privacyService.SanitizeLogMessage(ref message);
// Assert
message.Should().Be("Processing request for telegram_id=[REDACTED]");
}
[Theory]
[InlineData("")]
[InlineData("Normal log message without PII")]
[InlineData("Order ID: 12345")]
public void SanitizeLogMessage_WithoutPII_ShouldRemainUnchanged(string message)
{
// Arrange
var original = message;
// Act
_privacyService.SanitizeLogMessage(ref message);
// Assert
message.Should().Be(original);
}
}
}

View File

@@ -0,0 +1,215 @@
using System;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using TeleBot.Models;
using TeleBot.Services;
using Xunit;
namespace TeleBot.Tests.Services
{
public class SessionManagerTests
{
private readonly SessionManager _sessionManager;
private readonly Mock<IConfiguration> _mockConfiguration;
private readonly Mock<ILogger<SessionManager>> _mockLogger;
private readonly Mock<IPrivacyService> _mockPrivacyService;
private readonly Mock<IDistributedCache> _mockCache;
public SessionManagerTests()
{
_mockConfiguration = new Mock<IConfiguration>();
_mockLogger = new Mock<ILogger<SessionManager>>();
_mockPrivacyService = new Mock<IPrivacyService>();
_mockCache = new Mock<IDistributedCache>();
// Set up default configuration
_mockConfiguration.Setup(x => x["Privacy:SessionTimeoutMinutes"])
.Returns("30");
_mockConfiguration.Setup(x => x["Privacy:EphemeralByDefault"])
.Returns("true");
_mockConfiguration.Setup(x => x["Redis:Enabled"])
.Returns("false");
// Set up privacy service
_mockPrivacyService.Setup(x => x.HashIdentifier(It.IsAny<long>()))
.Returns<long>(id => $"HASH_{id}");
_sessionManager = new SessionManager(
_mockConfiguration.Object,
_mockLogger.Object,
_mockPrivacyService.Object,
null // No cache for unit tests
);
}
[Fact]
public async Task GetOrCreateSessionAsync_NewUser_ShouldCreateSession()
{
// Arrange
long telegramUserId = 123456;
// Act
var session = await _sessionManager.GetOrCreateSessionAsync(telegramUserId);
// Assert
session.Should().NotBeNull();
session.HashedUserId.Should().Be($"HASH_{telegramUserId}");
session.IsEphemeral.Should().BeTrue();
session.State.Should().Be(SessionState.MainMenu);
session.Cart.Should().NotBeNull();
session.Cart.IsEmpty().Should().BeTrue();
}
[Fact]
public async Task GetOrCreateSessionAsync_ExistingUser_ShouldReturnSameSession()
{
// Arrange
long telegramUserId = 123456;
// Act
var session1 = await _sessionManager.GetOrCreateSessionAsync(telegramUserId);
var session2 = await _sessionManager.GetOrCreateSessionAsync(telegramUserId);
// Assert
session1.Id.Should().Be(session2.Id);
session1.HashedUserId.Should().Be(session2.HashedUserId);
}
[Fact]
public async Task UpdateSessionAsync_ShouldUpdateSession()
{
// Arrange
long telegramUserId = 123456;
var session = await _sessionManager.GetOrCreateSessionAsync(telegramUserId);
// Act
session.State = SessionState.BrowsingCategories;
session.Cart.AddItem(Guid.NewGuid(), "Test Product", 10.00m, 2);
await _sessionManager.UpdateSessionAsync(session);
var retrievedSession = await _sessionManager.GetOrCreateSessionAsync(telegramUserId);
// Assert
retrievedSession.State.Should().Be(SessionState.BrowsingCategories);
retrievedSession.Cart.GetTotalItems().Should().Be(2);
}
[Fact]
public async Task DeleteSessionAsync_ShouldRemoveSession()
{
// Arrange
long telegramUserId = 123456;
var session = await _sessionManager.GetOrCreateSessionAsync(telegramUserId);
var sessionId = session.Id;
// Act
await _sessionManager.DeleteSessionAsync(sessionId);
var newSession = await _sessionManager.GetOrCreateSessionAsync(telegramUserId);
// Assert
newSession.Id.Should().NotBe(sessionId, "Should create a new session after deletion");
}
[Fact]
public async Task DeleteUserDataAsync_ShouldRemoveAllUserSessions()
{
// Arrange
long telegramUserId = 123456;
var session = await _sessionManager.GetOrCreateSessionAsync(telegramUserId);
var originalSessionId = session.Id;
// Act
await _sessionManager.DeleteUserDataAsync(telegramUserId);
var newSession = await _sessionManager.GetOrCreateSessionAsync(telegramUserId);
// Assert
newSession.Id.Should().NotBe(originalSessionId);
newSession.Cart.IsEmpty().Should().BeTrue();
newSession.State.Should().Be(SessionState.MainMenu);
}
[Fact]
public async Task CleanupExpiredSessionsAsync_ShouldRemoveExpiredSessions()
{
// Arrange
long userId1 = 111111;
long userId2 = 222222;
var session1 = await _sessionManager.GetOrCreateSessionAsync(userId1);
session1.ExpiresAt = DateTime.UtcNow.AddMinutes(-1); // Expired
await _sessionManager.UpdateSessionAsync(session1);
var session2 = await _sessionManager.GetOrCreateSessionAsync(userId2);
// session2 not expired
// Act
await _sessionManager.CleanupExpiredSessionsAsync();
var retrievedSession1 = await _sessionManager.GetOrCreateSessionAsync(userId1);
var retrievedSession2 = await _sessionManager.GetOrCreateSessionAsync(userId2);
// Assert
retrievedSession1.Id.Should().NotBe(session1.Id, "Expired session should be recreated");
retrievedSession2.Id.Should().Be(session2.Id, "Non-expired session should remain");
}
[Fact]
public void Session_IsExpired_ShouldReturnCorrectStatus()
{
// Arrange
var expiredSession = new UserSession
{
ExpiresAt = DateTime.UtcNow.AddMinutes(-1)
};
var activeSession = new UserSession
{
ExpiresAt = DateTime.UtcNow.AddMinutes(30)
};
// Assert
expiredSession.IsExpired().Should().BeTrue();
activeSession.IsExpired().Should().BeFalse();
}
[Fact]
public void Session_UpdateActivity_ShouldUpdateLastActivityTime()
{
// Arrange
var session = new UserSession();
var originalTime = session.LastActivityAt;
// Act
System.Threading.Thread.Sleep(10); // Small delay
session.UpdateActivity();
// Assert
session.LastActivityAt.Should().BeAfter(originalTime);
}
[Fact]
public async Task GetOrCreateSessionAsync_WithPrivacySettings_ShouldApplyDefaults()
{
// Arrange
_mockConfiguration.Setup(x => x["Privacy:EphemeralByDefault"])
.Returns("true");
_mockConfiguration.Setup(x => x["Features:EnableDisappearingMessages"])
.Returns("true");
long telegramUserId = 999999;
// Act
var session = await _sessionManager.GetOrCreateSessionAsync(telegramUserId);
// Assert
session.Privacy.Should().NotBeNull();
session.Privacy.UseEphemeralMode.Should().BeTrue();
session.Privacy.DisableAnalytics.Should().BeTrue();
session.Privacy.EnableDisappearingMessages.Should().BeTrue();
}
}
}