Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Orbit.Application/Profile/Queries/GetProfileQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
namespace Orbit.Application.Profile.Queries;

public record ProfileResponse(
Guid UserId,
string Name,
string Email,
string? TimeZone,
Expand Down Expand Up @@ -99,6 +100,7 @@ public async Task<Result<ProfileResponse>> Handle(GetProfileQuery request, Cance
user.PublicProfileShowTopHabits);

return Result.Success(new ProfileResponse(
user.Id,
user.Name,
user.Email,
user.TimeZone,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Orbit.Domain.Entities;
using Orbit.Domain.Interfaces;
using System.Linq.Expressions;
using System.Text.Json;

namespace Orbit.Application.Tests.Queries.Profile;

Expand Down Expand Up @@ -77,9 +78,101 @@
var result = await _handler.Handle(query, CancellationToken.None);

result.IsSuccess.Should().BeTrue();
result.Value.Name.Should().Be("John Doe");
result.Value.Email.Should().Be("test@example.com");
result.Value.AiMessagesLimit.Should().Be(20);
result.Value.Should().BeEquivalentTo(new
{
Name = "John Doe",
Email = "test@example.com",
user.TimeZone,
user.AiMemoryEnabled,
user.AiSummaryEnabled,
user.HasCompletedOnboarding,
user.HasCompletedTour,
user.HasCreatedFirstHabit,
user.HasLoggedFirstHabit,
user.HasTriedAstra,
user.HasCompletedOnboardingChecklist,
user.Language,
Plan = "pro",
user.HasProAccess,
user.IsTrialActive,
user.TrialEndsAt,
user.PlanExpiresAt,
AiMessagesUsed = user.AiMessagesUsedThisMonth,
AiMessagesLimit = 20,
user.HasImportedCalendar,
user.HasSeenImportPrompt,
HasGoogleConnection = false,
SubscriptionInterval = (string?)null,
SubscriptionSource = (string?)null,
user.IsLifetimePro,
user.WeekStartDay,
user.TotalXp,
Level = 1,
LevelTitle = "Starter",
AdRewardsClaimedToday = 0,
user.CurrentStreak,
user.LongestStreak,
StreakFreezesAvailable = 3,
user.ThemePreference,
user.ColorScheme,
user.GoogleCalendarAutoSyncEnabled,
GoogleCalendarAutoSyncStatus = Orbit.Domain.Enums.GoogleCalendarAutoSyncStatus.Idle,
user.GoogleCalendarLastSyncedAt,
CanViewGamification = true,
user.Handle,
user.SocialOptIn,
Uses24HourClock = true,
PublicProfile = new
{
Enabled = false,
Slug = (string?)null,
ShareUrl = (string?)null,
ShowStreak = true,
ShowLevel = true,
ShowAchievements = true,
ShowTopHabits = false
},
user.ProactiveAstraEnabled,
user.MarketingEmailConsent
});
}

[Fact]
public async Task Handle_UserFound_ReturnsCallerIdInDistinctIdFormat()
{
var user = CreateTestUser();
var callerId = user.Id;
_userRepo.GetByIdAsync(callerId, Arg.Any<CancellationToken>()).Returns(user);
_payGate.GetAiMessageLimit(callerId, Arg.Any<CancellationToken>()).Returns(20);
_userDateService.GetUserTodayAsync(callerId, Arg.Any<CancellationToken>()).Returns(Today);
StubFreezeRepoEmpty();

var result = await _handler.Handle(new GetProfileQuery(callerId), CancellationToken.None);

result.IsSuccess.Should().BeTrue();
result.Value.UserId.Should().Be(callerId);
result.Value.UserId.ToString().Should().Be(callerId.ToString("D").ToLowerInvariant());
result.Value.UserId.ToString().Should().MatchRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
}

[Fact]
public async Task ProfileResponse_LegacyConsumer_IgnoresUserId()
{
var user = CreateTestUser("Legacy User");
var callerId = user.Id;
_userRepo.GetByIdAsync(callerId, Arg.Any<CancellationToken>()).Returns(user);
_payGate.GetAiMessageLimit(callerId, Arg.Any<CancellationToken>()).Returns(20);
_userDateService.GetUserTodayAsync(callerId, Arg.Any<CancellationToken>()).Returns(Today);
StubFreezeRepoEmpty();
var result = await _handler.Handle(new GetProfileQuery(callerId), CancellationToken.None);

var json = JsonSerializer.Serialize(result.Value, new JsonSerializerOptions(JsonSerializerDefaults.Web));

Check warning on line 169 in tests/Orbit.Application.Tests/Queries/Profile/GetProfileQueryHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead.

See more on https://sonarcloud.io/project/issues?id=thomasluizon_orbit-api&issues=AZ_Z85WeRt4gBeCJFc5Y&open=AZ_Z85WeRt4gBeCJFc5Y&pullRequest=453
var legacyProfile = JsonSerializer.Deserialize<LegacyProfileResponse>(
json,
new JsonSerializerOptions(JsonSerializerDefaults.Web));

Check warning on line 172 in tests/Orbit.Application.Tests/Queries/Profile/GetProfileQueryHandlerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead.

See more on https://sonarcloud.io/project/issues?id=thomasluizon_orbit-api&issues=AZ_Z85WeRt4gBeCJFc5Z&open=AZ_Z85WeRt4gBeCJFc5Z&pullRequest=453

json.Should().Contain($"\"userId\":\"{callerId:D}\"");
legacyProfile.Should().Be(new LegacyProfileResponse("Legacy User", "test@example.com"));
}

[Fact]
Expand Down Expand Up @@ -134,6 +227,8 @@
result.IsFailure.Should().BeTrue();
result.Error.Should().Contain("User not found");
result.ErrorCode.Should().Be("USER_NOT_FOUND");
var readValue = () => result.Value;
readValue.Should().Throw<InvalidOperationException>();
}

[Fact]
Expand Down Expand Up @@ -314,4 +409,6 @@
result.Value.Level.Should().Be(12);
result.Value.LevelTitle.Should().Be("Legend");
}

private sealed record LegacyProfileResponse(string Name, string Email);
}
1 change: 1 addition & 0 deletions tests/Orbit.Infrastructure.Tests/Mcp/ProfileToolsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ private async Task<AgentExecuteOperationRequest> CapturedRequestAsync(Func<Task>
public async Task GetProfile_Success_ReturnsFormattedProfile()
{
var profile = new ProfileResponse(
Guid.NewGuid(),
"Thomas", "thomas@example.com", "America/Sao_Paulo",
true, true, true, true, true, true, true, true, "pt-BR", "Pro", true, false, null, null,
5, 100, false, false, false, null, null, false, 1, 500, 5, "Achiever",
Expand Down
Loading