Implement Bound 5 local social challenge bundle
This commit is contained in:
parent
e2a29fc139
commit
bc9ad4cd84
7 changed files with 2628 additions and 0 deletions
|
|
@ -103,6 +103,31 @@ namespace HyperTwistTrainingPersistenceLibraryInternal
|
|||
{
|
||||
ConsiderLatestUtc(LatestUtc, DrillBatch.LastUpdatedAtUtc);
|
||||
}
|
||||
for (const FHyperTwistTrainingFriendLinkRecord& FriendLinkRecord : RepositoryState.StoredFriendLinks)
|
||||
{
|
||||
ConsiderLatestUtc(LatestUtc, FriendLinkRecord.RequestedAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, FriendLinkRecord.RespondedAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, FriendLinkRecord.LastUpdatedAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, FriendLinkRecord.FriendSnapshot.LastPublishedAtUtc);
|
||||
}
|
||||
for (const FHyperTwistTrainingNotificationRecord& NotificationRecord : RepositoryState.StoredNotifications)
|
||||
{
|
||||
ConsiderLatestUtc(LatestUtc, NotificationRecord.CreatedAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, NotificationRecord.ReadAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, NotificationRecord.AcknowledgedAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, NotificationRecord.ArchivedAtUtc);
|
||||
}
|
||||
for (const FHyperTwistTrainingChallengeRecord& ChallengeRecord : RepositoryState.StoredChallenges)
|
||||
{
|
||||
ConsiderLatestUtc(LatestUtc, ChallengeRecord.CreatedAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, ChallengeRecord.LastUpdatedAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, ChallengeRecord.RespondedAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, ChallengeRecord.ReadyAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, ChallengeRecord.CompletedAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, ChallengeRecord.ExpiresAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, ChallengeRecord.ChallengerSnapshot.ReferenceEntry.RecordedAtUtc);
|
||||
ConsiderLatestUtc(LatestUtc, ChallengeRecord.RecipientSnapshot.ReferenceEntry.RecordedAtUtc);
|
||||
}
|
||||
for (const FHyperTwistTrainingCoachActionPlan& ActionPlan : RepositoryState.StoredCoachActionPlans)
|
||||
{
|
||||
ConsiderLatestUtc(LatestUtc, ActionPlan.CompletedAtUtc);
|
||||
|
|
@ -180,6 +205,31 @@ namespace HyperTwistTrainingPersistenceLibraryInternal
|
|||
OutUserIds.Add(DrillBatch.UserId);
|
||||
}
|
||||
}
|
||||
for (const FHyperTwistTrainingFriendLinkRecord& FriendLinkRecord : RepositoryState.StoredFriendLinks)
|
||||
{
|
||||
if (!FriendLinkRecord.UserId.IsEmpty())
|
||||
{
|
||||
OutUserIds.Add(FriendLinkRecord.UserId);
|
||||
}
|
||||
}
|
||||
for (const FHyperTwistTrainingNotificationRecord& NotificationRecord : RepositoryState.StoredNotifications)
|
||||
{
|
||||
if (!NotificationRecord.RecipientUserId.IsEmpty())
|
||||
{
|
||||
OutUserIds.Add(NotificationRecord.RecipientUserId);
|
||||
}
|
||||
}
|
||||
for (const FHyperTwistTrainingChallengeRecord& ChallengeRecord : RepositoryState.StoredChallenges)
|
||||
{
|
||||
if (!ChallengeRecord.ChallengerUserId.IsEmpty())
|
||||
{
|
||||
OutUserIds.Add(ChallengeRecord.ChallengerUserId);
|
||||
}
|
||||
if (!ChallengeRecord.RecipientUserId.IsEmpty())
|
||||
{
|
||||
OutUserIds.Add(ChallengeRecord.RecipientUserId);
|
||||
}
|
||||
}
|
||||
for (const FHyperTwistTrainingCoachActionPlan& ActionPlan : RepositoryState.StoredCoachActionPlans)
|
||||
{
|
||||
if (!ActionPlan.UserId.IsEmpty())
|
||||
|
|
@ -445,6 +495,9 @@ UHyperTwistTrainingPersistenceLibrary::VerifyTrainingRepositoryStateRoundTrip(
|
|||
Verification.ExpectedStoredSessionTemplateCount = ExpectedRepositoryState.StoredSessionTemplates.Num();
|
||||
Verification.ExpectedMethodDrillFavoriteCount = ExpectedRepositoryState.StoredMethodDrillFavorites.Num();
|
||||
Verification.ExpectedMethodDrillBatchCount = ExpectedRepositoryState.StoredMethodDrillBatches.Num();
|
||||
Verification.ExpectedFriendLinkCount = ExpectedRepositoryState.StoredFriendLinks.Num();
|
||||
Verification.ExpectedNotificationCount = ExpectedRepositoryState.StoredNotifications.Num();
|
||||
Verification.ExpectedChallengeCount = ExpectedRepositoryState.StoredChallenges.Num();
|
||||
Verification.ExpectedCoachActionPlanCount = ExpectedRepositoryState.StoredCoachActionPlans.Num();
|
||||
Verification.ExpectedCoachSessionQueueCount = ExpectedRepositoryState.StoredCoachSessionQueues.Num();
|
||||
Verification.ExpectedCoachSessionQueueHistoryCount =
|
||||
|
|
@ -494,6 +547,9 @@ UHyperTwistTrainingPersistenceLibrary::VerifyTrainingRepositoryStateRoundTrip(
|
|||
Verification.LoadedStoredSessionTemplateCount = LoadedRepositoryState.StoredSessionTemplates.Num();
|
||||
Verification.LoadedMethodDrillFavoriteCount = LoadedRepositoryState.StoredMethodDrillFavorites.Num();
|
||||
Verification.LoadedMethodDrillBatchCount = LoadedRepositoryState.StoredMethodDrillBatches.Num();
|
||||
Verification.LoadedFriendLinkCount = LoadedRepositoryState.StoredFriendLinks.Num();
|
||||
Verification.LoadedNotificationCount = LoadedRepositoryState.StoredNotifications.Num();
|
||||
Verification.LoadedChallengeCount = LoadedRepositoryState.StoredChallenges.Num();
|
||||
Verification.LoadedCoachActionPlanCount = LoadedRepositoryState.StoredCoachActionPlans.Num();
|
||||
Verification.LoadedCoachSessionQueueCount = LoadedRepositoryState.StoredCoachSessionQueues.Num();
|
||||
Verification.LoadedCoachSessionQueueHistoryCount =
|
||||
|
|
@ -511,6 +567,9 @@ UHyperTwistTrainingPersistenceLibrary::VerifyTrainingRepositoryStateRoundTrip(
|
|||
&& Verification.ExpectedStoredSessionTemplateCount == Verification.LoadedStoredSessionTemplateCount
|
||||
&& Verification.ExpectedMethodDrillFavoriteCount == Verification.LoadedMethodDrillFavoriteCount
|
||||
&& Verification.ExpectedMethodDrillBatchCount == Verification.LoadedMethodDrillBatchCount
|
||||
&& Verification.ExpectedFriendLinkCount == Verification.LoadedFriendLinkCount
|
||||
&& Verification.ExpectedNotificationCount == Verification.LoadedNotificationCount
|
||||
&& Verification.ExpectedChallengeCount == Verification.LoadedChallengeCount
|
||||
&& Verification.ExpectedCoachActionPlanCount == Verification.LoadedCoachActionPlanCount
|
||||
&& Verification.ExpectedCoachSessionQueueCount == Verification.LoadedCoachSessionQueueCount
|
||||
&& Verification.ExpectedCoachSessionQueueHistoryCount
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -3069,6 +3069,11 @@ FHyperTwistTrainingPublicationBundle UHyperTwistTrainingSubsystem::GetActivePubl
|
|||
return ActivePublicationBundle;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingSocialChallengeBundle UHyperTwistTrainingSubsystem::GetActiveSocialChallengeBundle() const
|
||||
{
|
||||
return ActiveSocialChallengeBundle;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingSubsystem::GetActiveCoachMemorySnapshot() const
|
||||
{
|
||||
return ActiveCoachMemorySnapshot;
|
||||
|
|
@ -3233,6 +3238,30 @@ TArray<FHyperTwistTrainingMethodDrillBatch> UHyperTwistTrainingSubsystem::GetRes
|
|||
);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingFriendLinkRecord> UHyperTwistTrainingSubsystem::GetFriendLinksForUser(
|
||||
const FString& UserId
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::ListFriendLinksForUser(TrainingRepositoryState, UserId);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingNotificationRecord> UHyperTwistTrainingSubsystem::GetNotificationsForUser(
|
||||
const FString& UserId
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::ListNotificationsForUser(
|
||||
TrainingRepositoryState,
|
||||
UserId
|
||||
);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingChallengeRecord> UHyperTwistTrainingSubsystem::GetChallengesForUser(
|
||||
const FString& UserId
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::ListChallengesForUser(TrainingRepositoryState, UserId);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingMethodDrillBatchReadiness UHyperTwistTrainingSubsystem::DeriveMethodDrillBatchReadiness(
|
||||
const FHyperTwistTrainingMethodDrillBatch& DrillBatch,
|
||||
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition
|
||||
|
|
@ -3373,6 +3402,44 @@ bool UHyperTwistTrainingSubsystem::TryGetStoredCoachActionPlan(
|
|||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetStoredFriendLinkRecord(
|
||||
const FString& UserId,
|
||||
const FString& FriendUserId,
|
||||
FHyperTwistTrainingFriendLinkRecord& OutFriendLinkRecord
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::TryGetFriendLinkRecord(
|
||||
TrainingRepositoryState,
|
||||
UserId,
|
||||
FriendUserId,
|
||||
OutFriendLinkRecord
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetStoredNotificationRecord(
|
||||
const FString& NotificationId,
|
||||
FHyperTwistTrainingNotificationRecord& OutNotificationRecord
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::TryGetNotificationRecord(
|
||||
TrainingRepositoryState,
|
||||
NotificationId,
|
||||
OutNotificationRecord
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetStoredChallengeRecord(
|
||||
const FString& ChallengeId,
|
||||
FHyperTwistTrainingChallengeRecord& OutChallengeRecord
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::TryGetChallengeRecord(
|
||||
TrainingRepositoryState,
|
||||
ChallengeId,
|
||||
OutChallengeRecord
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetStoredCoachSessionQueueState(
|
||||
const FString& UserId,
|
||||
const FString& QueueId,
|
||||
|
|
@ -3419,6 +3486,105 @@ bool UHyperTwistTrainingSubsystem::SaveTrainingSessionTemplate(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::SaveFriendLinkRecord(
|
||||
const FHyperTwistTrainingFriendLinkRecord& FriendLinkRecord
|
||||
)
|
||||
{
|
||||
if (!FriendLinkRecord.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertFriendLinkRecord(
|
||||
TrainingRepositoryState,
|
||||
FriendLinkRecord
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::SaveNotificationRecord(
|
||||
const FHyperTwistTrainingNotificationRecord& NotificationRecord
|
||||
)
|
||||
{
|
||||
if (!NotificationRecord.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertNotificationRecord(
|
||||
TrainingRepositoryState,
|
||||
NotificationRecord
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::MarkNotificationRead(const FString& NotificationId)
|
||||
{
|
||||
if (NotificationId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::MarkNotificationRead(
|
||||
TrainingRepositoryState,
|
||||
NotificationId,
|
||||
FDateTime::UtcNow().ToIso8601()
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::AcknowledgeNotification(const FString& NotificationId)
|
||||
{
|
||||
if (NotificationId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::AcknowledgeNotification(
|
||||
TrainingRepositoryState,
|
||||
NotificationId,
|
||||
FDateTime::UtcNow().ToIso8601()
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::ArchiveNotification(const FString& NotificationId)
|
||||
{
|
||||
if (NotificationId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::ArchiveNotification(
|
||||
TrainingRepositoryState,
|
||||
NotificationId,
|
||||
FDateTime::UtcNow().ToIso8601()
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::SaveChallengeRecord(
|
||||
const FHyperTwistTrainingChallengeRecord& ChallengeRecord
|
||||
)
|
||||
{
|
||||
if (!ChallengeRecord.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertChallengeRecord(
|
||||
TrainingRepositoryState,
|
||||
ChallengeRecord
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::SaveMethodDrillFavorite(
|
||||
const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry
|
||||
)
|
||||
|
|
@ -6947,6 +7113,7 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
ActiveLearnerDeckStateSummary = FHyperTwistTrainingLearnerDeckStateSummary();
|
||||
ActiveLearnerProfile = FHyperTwistTrainingLearnerProfile();
|
||||
ActivePublicationBundle = FHyperTwistTrainingPublicationBundle();
|
||||
ActiveSocialChallengeBundle = FHyperTwistTrainingSocialChallengeBundle();
|
||||
ActiveCoachMemorySnapshot = FHyperTwistTrainingCoachMemorySnapshot();
|
||||
ActiveReplayReviewAnalytics = FHyperTwistReplayReviewAnalytics();
|
||||
ActiveRecognitionReplaySummary = FHyperTwistRecognitionReplaySummary();
|
||||
|
|
@ -7192,6 +7359,12 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
)
|
||||
: FHyperTwistTrainingPublicationBundle();
|
||||
}
|
||||
ActiveSocialChallengeBundle = UHyperTwistTrainingRepositoryLibrary::DeriveSocialChallengeBundle(
|
||||
TrainingRepositoryState,
|
||||
RepositoryViewUserId,
|
||||
ResolvedReferenceUtc,
|
||||
ActivePublicationBundle.EntitlementGate
|
||||
);
|
||||
ActiveReviewFlowStatus = ActiveReviewPlanState.IsStructurallyValid()
|
||||
? UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState)
|
||||
: FHyperTwistTrainingReviewFlowStatus();
|
||||
|
|
|
|||
|
|
@ -130,6 +130,113 @@ public:
|
|||
const FString& TemplateId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static TArray<FHyperTwistTrainingFriendLinkRecord> ListFriendLinksForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository|Social")
|
||||
static bool TryGetFriendLinkRecord(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& FriendUserId,
|
||||
FHyperTwistTrainingFriendLinkRecord& OutFriendLinkRecord
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static FHyperTwistTrainingRepositoryState UpsertFriendLinkRecord(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingFriendLinkRecord& FriendLinkRecord
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static FHyperTwistTrainingFriendLinkRecord ResolveFriendLinkRecordState(
|
||||
const FHyperTwistTrainingFriendLinkRecord& FriendLinkRecord,
|
||||
EHyperTwistTrainingFriendLinkState State,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static TArray<FHyperTwistTrainingNotificationRecord> ListNotificationsForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository|Social")
|
||||
static bool TryGetNotificationRecord(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& NotificationId,
|
||||
FHyperTwistTrainingNotificationRecord& OutNotificationRecord
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static FHyperTwistTrainingRepositoryState UpsertNotificationRecord(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingNotificationRecord& NotificationRecord
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static FHyperTwistTrainingRepositoryState MarkNotificationRead(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& NotificationId,
|
||||
const FString& ReadAtUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static FHyperTwistTrainingRepositoryState AcknowledgeNotification(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& NotificationId,
|
||||
const FString& AcknowledgedAtUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static FHyperTwistTrainingRepositoryState ArchiveNotification(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& NotificationId,
|
||||
const FString& ArchivedAtUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static TArray<FHyperTwistTrainingChallengeRecord> ListChallengesForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository|Social")
|
||||
static bool TryGetChallengeRecord(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& ChallengeId,
|
||||
FHyperTwistTrainingChallengeRecord& OutChallengeRecord
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static FHyperTwistTrainingRepositoryState UpsertChallengeRecord(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingChallengeRecord& ChallengeRecord
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static FHyperTwistTrainingChallengeRecord ResolveChallengeRecordState(
|
||||
const FHyperTwistTrainingChallengeRecord& ChallengeRecord,
|
||||
EHyperTwistTrainingChallengeLifecycleState LifecycleState,
|
||||
EHyperTwistTrainingLocalMatchReadinessState MatchReadinessState,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static FHyperTwistTrainingChallengeComparisonBundle BuildChallengeComparisonBundle(
|
||||
const FHyperTwistTrainingChallengeRecord& ChallengeRecord
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Social")
|
||||
static FHyperTwistTrainingSocialChallengeBundle DeriveSocialChallengeBundle(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc,
|
||||
const FHyperTwistTrainingEntitlementGateState& EntitlementGateState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|SmartDevice")
|
||||
static FHyperTwistTrainingRepositoryState UpsertSmartDeviceSessionRecord(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
|
|
|
|||
|
|
@ -161,6 +161,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Publication")
|
||||
FHyperTwistTrainingPublicationBundle GetActivePublicationBundle() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Social")
|
||||
FHyperTwistTrainingSocialChallengeBundle GetActiveSocialChallengeBundle() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingCoachMemorySnapshot GetActiveCoachMemorySnapshot() const;
|
||||
|
||||
|
|
@ -257,6 +260,15 @@ public:
|
|||
const FString& MethodSegmentId
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Social")
|
||||
TArray<FHyperTwistTrainingFriendLinkRecord> GetFriendLinksForUser(const FString& UserId) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Social")
|
||||
TArray<FHyperTwistTrainingNotificationRecord> GetNotificationsForUser(const FString& UserId) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Social")
|
||||
TArray<FHyperTwistTrainingChallengeRecord> GetChallengesForUser(const FString& UserId) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
|
||||
FHyperTwistTrainingMethodDrillBatchReadiness DeriveMethodDrillBatchReadiness(
|
||||
const FHyperTwistTrainingMethodDrillBatch& DrillBatch,
|
||||
|
|
@ -321,6 +333,25 @@ public:
|
|||
FHyperTwistTrainingCoachActionPlan& OutActionPlan
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Social")
|
||||
bool TryGetStoredFriendLinkRecord(
|
||||
const FString& UserId,
|
||||
const FString& FriendUserId,
|
||||
FHyperTwistTrainingFriendLinkRecord& OutFriendLinkRecord
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Social")
|
||||
bool TryGetStoredNotificationRecord(
|
||||
const FString& NotificationId,
|
||||
FHyperTwistTrainingNotificationRecord& OutNotificationRecord
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Social")
|
||||
bool TryGetStoredChallengeRecord(
|
||||
const FString& ChallengeId,
|
||||
FHyperTwistTrainingChallengeRecord& OutChallengeRecord
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
|
||||
bool TryGetStoredCoachSessionQueueState(
|
||||
const FString& UserId,
|
||||
|
|
@ -338,6 +369,24 @@ public:
|
|||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
bool SaveTrainingSessionTemplate(const FHyperTwistTrainingSessionTemplate& SessionTemplate);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Social")
|
||||
bool SaveFriendLinkRecord(const FHyperTwistTrainingFriendLinkRecord& FriendLinkRecord);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Social")
|
||||
bool SaveNotificationRecord(const FHyperTwistTrainingNotificationRecord& NotificationRecord);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Social")
|
||||
bool MarkNotificationRead(const FString& NotificationId);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Social")
|
||||
bool AcknowledgeNotification(const FString& NotificationId);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Social")
|
||||
bool ArchiveNotification(const FString& NotificationId);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Social")
|
||||
bool SaveChallengeRecord(const FHyperTwistTrainingChallengeRecord& ChallengeRecord);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
|
||||
bool SaveMethodDrillFavorite(const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry);
|
||||
|
||||
|
|
@ -756,6 +805,9 @@ private:
|
|||
UPROPERTY()
|
||||
FHyperTwistTrainingPublicationBundle ActivePublicationBundle;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingSocialChallengeBundle ActiveSocialChallengeBundle;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingCoachMemorySnapshot ActiveCoachMemorySnapshot;
|
||||
|
||||
|
|
|
|||
|
|
@ -3515,6 +3515,459 @@ struct FHyperTwistTrainingPublicationBundle
|
|||
}
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistTrainingFriendLinkState : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "None"),
|
||||
PendingOutgoing UMETA(DisplayName = "Pending Outgoing"),
|
||||
PendingIncoming UMETA(DisplayName = "Pending Incoming"),
|
||||
Active UMETA(DisplayName = "Active"),
|
||||
Rejected UMETA(DisplayName = "Rejected")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistTrainingNotificationKind : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "None"),
|
||||
FriendRequest UMETA(DisplayName = "Friend Request"),
|
||||
FriendAccepted UMETA(DisplayName = "Friend Accepted"),
|
||||
FriendActivity UMETA(DisplayName = "Friend Activity"),
|
||||
ChallengeInvitation UMETA(DisplayName = "Challenge Invitation"),
|
||||
ChallengeAccepted UMETA(DisplayName = "Challenge Accepted"),
|
||||
ChallengeReady UMETA(DisplayName = "Challenge Ready"),
|
||||
ChallengeExpired UMETA(DisplayName = "Challenge Expired"),
|
||||
ChallengeCompleted UMETA(DisplayName = "Challenge Completed")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistTrainingNotificationVisibilityState : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "None"),
|
||||
Hidden UMETA(DisplayName = "Hidden"),
|
||||
Inbox UMETA(DisplayName = "Inbox"),
|
||||
Archived UMETA(DisplayName = "Archived")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistTrainingChallengeLifecycleState : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "None"),
|
||||
Pending UMETA(DisplayName = "Pending"),
|
||||
Accepted UMETA(DisplayName = "Accepted"),
|
||||
Rejected UMETA(DisplayName = "Rejected"),
|
||||
Expired UMETA(DisplayName = "Expired"),
|
||||
Ready UMETA(DisplayName = "Ready"),
|
||||
Completed UMETA(DisplayName = "Completed")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistTrainingLocalMatchReadinessState : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "None"),
|
||||
Seeded UMETA(DisplayName = "Seeded"),
|
||||
AwaitingAttempts UMETA(DisplayName = "Awaiting Attempts"),
|
||||
ReadyToCompare UMETA(DisplayName = "Ready To Compare"),
|
||||
ComparisonLocked UMETA(DisplayName = "Comparison Locked")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingFriendIdentitySnapshot
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FriendUserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DisplayName;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PublicationId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Headline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LastPublishedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PreferredEventId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> Tags;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !FriendUserId.IsEmpty() && !DisplayName.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingFriendLinkRecord
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LinkId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingFriendIdentitySnapshot FriendSnapshot;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RequestedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RespondedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LastUpdatedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingFriendLinkState State = EHyperTwistTrainingFriendLinkState::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> Tags;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !LinkId.IsEmpty()
|
||||
&& !UserId.IsEmpty()
|
||||
&& FriendSnapshot.IsStructurallyValid()
|
||||
&& State != EHyperTwistTrainingFriendLinkState::None;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingNotificationRecord
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString NotificationId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RecipientUserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ActorUserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FriendUserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ChallengeId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EventId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString CreatedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ReadAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString AcknowledgedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ArchivedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Title;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SummaryLine;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString VisibilityReason;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingNotificationKind Kind = EHyperTwistTrainingNotificationKind::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingNotificationVisibilityState VisibilityState =
|
||||
EHyperTwistTrainingNotificationVisibilityState::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bRead = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bAcknowledged = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !NotificationId.IsEmpty()
|
||||
&& !RecipientUserId.IsEmpty()
|
||||
&& !CreatedAtUtc.IsEmpty()
|
||||
&& !Title.IsEmpty()
|
||||
&& Kind != EHyperTwistTrainingNotificationKind::None
|
||||
&& VisibilityState != EHyperTwistTrainingNotificationVisibilityState::None;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingChallengeParticipantSnapshot
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DisplayName;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PublicationId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Headline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingLeaderboardEntry ReferenceEntry;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !UserId.IsEmpty() && !DisplayName.IsEmpty() && !DeckId.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingChallengeRecord
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ChallengeId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ChallengerUserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RecipientUserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EventId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString CreatedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LastUpdatedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RespondedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ReadyAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString CompletedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ExpiresAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Title;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SummaryLine;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SeedLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingChallengeLifecycleState LifecycleState =
|
||||
EHyperTwistTrainingChallengeLifecycleState::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingLocalMatchReadinessState MatchReadinessState =
|
||||
EHyperTwistTrainingLocalMatchReadinessState::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingChallengeParticipantSnapshot ChallengerSnapshot;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingChallengeParticipantSnapshot RecipientSnapshot;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !ChallengeId.IsEmpty()
|
||||
&& !ChallengerUserId.IsEmpty()
|
||||
&& !RecipientUserId.IsEmpty()
|
||||
&& !DeckId.IsEmpty()
|
||||
&& !EventId.IsEmpty()
|
||||
&& !CreatedAtUtc.IsEmpty()
|
||||
&& LifecycleState != EHyperTwistTrainingChallengeLifecycleState::None
|
||||
&& ChallengerSnapshot.IsStructurallyValid()
|
||||
&& RecipientSnapshot.IsStructurallyValid();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingChallengeComparisonEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DisplayName;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EventId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EventTitle;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString MetricLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString MetricPrimaryValue;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString MetricSecondaryValue;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RelativeDateLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ProgramLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bCurrentUser = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bHasComparableEntry = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !UserId.IsEmpty() && !DisplayName.IsEmpty() && !EventId.IsEmpty() && !MetricLabel.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingChallengeComparisonBundle
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ChallengeId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EventId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EventTitle;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString MetricLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LifecycleLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString MatchReadinessLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingChallengeComparisonEntry> Entries;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bReadyToCompare = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !ChallengeId.IsEmpty() && !DeckId.IsEmpty() && !EventId.IsEmpty() && Entries.Num() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingSocialChallengeBundle
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ReferenceUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString GateId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FeatureId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString VisibilityLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bVisible = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bChallengeCreationEnabled = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingFriendLinkRecord> FriendLinks;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingNotificationRecord> Notifications;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingChallengeRecord> Challenges;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingChallengeComparisonBundle> ComparisonBundles;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 PendingFriendCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 VisibleNotificationCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 UnreadNotificationCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 AcknowledgedNotificationCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ArchivedNotificationCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ActiveChallengeCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ReadyChallengeCount = 0;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !UserId.IsEmpty() && !ReferenceUtc.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCoachMemorySnapshot
|
||||
{
|
||||
|
|
@ -8031,6 +8484,15 @@ struct FHyperTwistTrainingRepositoryState
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingMethodDrillBatch> StoredMethodDrillBatches;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingFriendLinkRecord> StoredFriendLinks;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingNotificationRecord> StoredNotifications;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingChallengeRecord> StoredChallenges;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingCoachActionPlan> StoredCoachActionPlans;
|
||||
|
||||
|
|
@ -8112,6 +8574,33 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateMethodDrillBatchCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalFriendLinkCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 InvalidFriendLinkCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateFriendLinkCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalNotificationCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 InvalidNotificationCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateNotificationCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalChallengeCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 InvalidChallengeCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateChallengeCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalCoachActionPlanCount = 0;
|
||||
|
||||
|
|
@ -8233,6 +8722,12 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
|| DuplicateMethodDrillFavoriteCount > 0
|
||||
|| InvalidMethodDrillBatchCount > 0
|
||||
|| DuplicateMethodDrillBatchCount > 0
|
||||
|| InvalidFriendLinkCount > 0
|
||||
|| DuplicateFriendLinkCount > 0
|
||||
|| InvalidNotificationCount > 0
|
||||
|| DuplicateNotificationCount > 0
|
||||
|| InvalidChallengeCount > 0
|
||||
|| DuplicateChallengeCount > 0
|
||||
|| InvalidCoachActionPlanCount > 0
|
||||
|| DuplicateCoachActionPlanCount > 0
|
||||
|| MissingCoachActionPlanSourceSessionCount > 0
|
||||
|
|
@ -8330,6 +8825,24 @@ struct FHyperTwistTrainingRepositoryRoundTripVerification
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 LoadedMethodDrillBatchCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ExpectedFriendLinkCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 LoadedFriendLinkCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ExpectedNotificationCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 LoadedNotificationCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ExpectedChallengeCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 LoadedChallengeCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ExpectedCoachActionPlanCount = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,534 @@
|
|||
// Copyright HyperTwist, Inc. All Rights Reserved.
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingPersistenceLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
|
||||
#include "UObject/Package.h"
|
||||
|
||||
#if WITH_AUTOMATION_TESTS
|
||||
|
||||
namespace HyperTwistCubeDeskBound5SocialChallengeTestInternal
|
||||
{
|
||||
FHyperTwistTrainingDeck MakeDeck(const FString& DeckId)
|
||||
{
|
||||
FHyperTwistTrainingDeck Deck;
|
||||
Deck.DeckId = DeckId;
|
||||
Deck.Title = TEXT("Bound5 Social Deck");
|
||||
Deck.DeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer};
|
||||
|
||||
FHyperTwistTrainingCase TrainingCase;
|
||||
TrainingCase.CaseId = TEXT("bound5-case-1");
|
||||
TrainingCase.PuzzleId = TEXT("3x3x3x3");
|
||||
TrainingCase.PromptLabel = TEXT("Bound5 Case");
|
||||
TrainingCase.AllowedDeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer};
|
||||
Deck.Cases.Add(TrainingCase);
|
||||
return Deck;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingEntitlementGateState MakeVisibleEntitledGate()
|
||||
{
|
||||
FHyperTwistTrainingEntitlementGateState GateState;
|
||||
GateState.GateId = TEXT("gate/local-social");
|
||||
GateState.FeatureId = TEXT("feature/local-social");
|
||||
GateState.Title = TEXT("Local Social");
|
||||
GateState.VisibilityLabel = TEXT("published");
|
||||
GateState.FeatureGovernanceContractId = TEXT("posthog-feature-governance-and-blast-radius");
|
||||
GateState.EarlyAccessContractId = TEXT("posthog-early-access-rollout");
|
||||
GateState.LifecycleStageId = TEXT("active");
|
||||
GateState.EnrollmentModeLabel = TEXT("automatic");
|
||||
GateState.ApprovedWorkflowTags = {TEXT("feature-governance"), TEXT("local-social")};
|
||||
GateState.bVisible = true;
|
||||
GateState.bEntitled = true;
|
||||
return GateState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingFriendLinkRecord MakeFriendLinkRecord(
|
||||
const FString& UserId,
|
||||
const FString& FriendUserId,
|
||||
const FString& DisplayName,
|
||||
const EHyperTwistTrainingFriendLinkState State,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingFriendLinkRecord FriendLinkRecord;
|
||||
FriendLinkRecord.LinkId = FString::Printf(TEXT("friend-link/%s/%s"), *UserId, *FriendUserId);
|
||||
FriendLinkRecord.UserId = UserId;
|
||||
FriendLinkRecord.FriendSnapshot.FriendUserId = FriendUserId;
|
||||
FriendLinkRecord.FriendSnapshot.DisplayName = DisplayName;
|
||||
FriendLinkRecord.FriendSnapshot.DeckId = TEXT("deck/hypercube-3333");
|
||||
FriendLinkRecord.FriendSnapshot.PublicationId =
|
||||
FString::Printf(TEXT("publication/%s/%s"), *FriendUserId, TEXT("deck/hypercube-3333"));
|
||||
FriendLinkRecord.FriendSnapshot.LastPublishedAtUtc = ReferenceUtc;
|
||||
FriendLinkRecord.Tags = {TEXT("local"), TEXT("bound5")};
|
||||
return UHyperTwistTrainingRepositoryLibrary::ResolveFriendLinkRecordState(
|
||||
FriendLinkRecord,
|
||||
State,
|
||||
ReferenceUtc
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingNotificationRecord MakeNotificationRecord(
|
||||
const FString& NotificationId,
|
||||
const FString& RecipientUserId,
|
||||
const FString& ActorUserId,
|
||||
const FString& FriendUserId,
|
||||
const FString& ChallengeId,
|
||||
const EHyperTwistTrainingNotificationKind Kind,
|
||||
const FString& CreatedAtUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingNotificationRecord NotificationRecord;
|
||||
NotificationRecord.NotificationId = NotificationId;
|
||||
NotificationRecord.RecipientUserId = RecipientUserId;
|
||||
NotificationRecord.ActorUserId = ActorUserId;
|
||||
NotificationRecord.FriendUserId = FriendUserId;
|
||||
NotificationRecord.ChallengeId = ChallengeId;
|
||||
NotificationRecord.DeckId = TEXT("deck/hypercube-3333");
|
||||
NotificationRecord.EventId = TEXT("event/3x3x3x3/single");
|
||||
NotificationRecord.CreatedAtUtc = CreatedAtUtc;
|
||||
NotificationRecord.Title = TEXT("Bound5 notification");
|
||||
NotificationRecord.SummaryLine = TEXT("Local social notification");
|
||||
NotificationRecord.Kind = Kind;
|
||||
NotificationRecord.VisibilityState = EHyperTwistTrainingNotificationVisibilityState::Inbox;
|
||||
return NotificationRecord;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingLeaderboardEntry MakeLeaderboardEntry(
|
||||
const FString& EntryId,
|
||||
const FString& DisplayName,
|
||||
const FString& PrimaryValue,
|
||||
const FString& SecondaryValue,
|
||||
const FString& RecordedAtUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingLeaderboardEntry Entry;
|
||||
Entry.EntryId = EntryId;
|
||||
Entry.EventId = TEXT("event/3x3x3x3/single");
|
||||
Entry.EventTitle = TEXT("3x3x3 Single");
|
||||
Entry.MetricLabel = TEXT("Single");
|
||||
Entry.SolverDisplayName = DisplayName;
|
||||
Entry.ProgramLabel = TEXT("HSC");
|
||||
Entry.RecordedAtUtc = RecordedAtUtc;
|
||||
Entry.RelativeDateLabel = TEXT("today");
|
||||
Entry.RankLabel = TEXT("#1 local");
|
||||
Entry.MetricPrimaryValue = PrimaryValue;
|
||||
Entry.MetricSecondaryValue = SecondaryValue;
|
||||
Entry.DeckId = TEXT("deck/hypercube-3333");
|
||||
Entry.bEligible = true;
|
||||
Entry.bPublished = true;
|
||||
return Entry;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingChallengeRecord MakeChallengeRecord(
|
||||
const FString& ChallengeId,
|
||||
const FString& ChallengerUserId,
|
||||
const FString& ChallengerDisplayName,
|
||||
const FString& RecipientUserId,
|
||||
const FString& RecipientDisplayName,
|
||||
const EHyperTwistTrainingChallengeLifecycleState LifecycleState,
|
||||
const EHyperTwistTrainingLocalMatchReadinessState MatchReadinessState,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingChallengeRecord ChallengeRecord;
|
||||
ChallengeRecord.ChallengeId = ChallengeId;
|
||||
ChallengeRecord.ChallengerUserId = ChallengerUserId;
|
||||
ChallengeRecord.RecipientUserId = RecipientUserId;
|
||||
ChallengeRecord.DeckId = TEXT("deck/hypercube-3333");
|
||||
ChallengeRecord.EventId = TEXT("event/3x3x3x3/single");
|
||||
ChallengeRecord.Title = TEXT("Bound5 challenge");
|
||||
ChallengeRecord.SummaryLine = TEXT("Local comparison challenge");
|
||||
ChallengeRecord.SeedLabel = TEXT("single");
|
||||
ChallengeRecord.ChallengerSnapshot.UserId = ChallengerUserId;
|
||||
ChallengeRecord.ChallengerSnapshot.DisplayName = ChallengerDisplayName;
|
||||
ChallengeRecord.ChallengerSnapshot.PublicationId =
|
||||
FString::Printf(TEXT("publication/%s/%s"), *ChallengerUserId, TEXT("deck/hypercube-3333"));
|
||||
ChallengeRecord.ChallengerSnapshot.DeckId = TEXT("deck/hypercube-3333");
|
||||
ChallengeRecord.ChallengerSnapshot.Headline = TEXT("Local challenger");
|
||||
ChallengeRecord.ChallengerSnapshot.ReferenceEntry = MakeLeaderboardEntry(
|
||||
FString::Printf(TEXT("%s-entry"), *ChallengerUserId),
|
||||
ChallengerDisplayName,
|
||||
TEXT("12.34"),
|
||||
TEXT("12340 ms"),
|
||||
ReferenceUtc
|
||||
);
|
||||
ChallengeRecord.RecipientSnapshot.UserId = RecipientUserId;
|
||||
ChallengeRecord.RecipientSnapshot.DisplayName = RecipientDisplayName;
|
||||
ChallengeRecord.RecipientSnapshot.PublicationId =
|
||||
FString::Printf(TEXT("publication/%s/%s"), *RecipientUserId, TEXT("deck/hypercube-3333"));
|
||||
ChallengeRecord.RecipientSnapshot.DeckId = TEXT("deck/hypercube-3333");
|
||||
ChallengeRecord.RecipientSnapshot.Headline = TEXT("Local recipient");
|
||||
ChallengeRecord.RecipientSnapshot.ReferenceEntry = MakeLeaderboardEntry(
|
||||
FString::Printf(TEXT("%s-entry"), *RecipientUserId),
|
||||
RecipientDisplayName,
|
||||
TEXT("13.21"),
|
||||
TEXT("13210 ms"),
|
||||
ReferenceUtc
|
||||
);
|
||||
return UHyperTwistTrainingRepositoryLibrary::ResolveChallengeRecordState(
|
||||
ChallengeRecord,
|
||||
LifecycleState,
|
||||
MatchReadinessState,
|
||||
ReferenceUtc
|
||||
);
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingChallengeComparisonEntry* FindCurrentUserEntry(
|
||||
const FHyperTwistTrainingChallengeComparisonBundle& ComparisonBundle
|
||||
)
|
||||
{
|
||||
return ComparisonBundle.Entries.FindByPredicate(
|
||||
[](const FHyperTwistTrainingChallengeComparisonEntry& Entry)
|
||||
{
|
||||
return Entry.bCurrentUser;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistCubeDeskBound5FriendLinkLifecycleTest,
|
||||
"HyperTwist.CleanRoom.CubeDesk.Bound5.FriendLinkLifecycle",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistCubeDeskBound5FriendLinkLifecycleTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState RepositoryState;
|
||||
FHyperTwistTrainingFriendLinkRecord FriendLinkRecord =
|
||||
HyperTwistCubeDeskBound5SocialChallengeTestInternal::MakeFriendLinkRecord(
|
||||
TEXT("bound5-user"),
|
||||
TEXT("friend-user"),
|
||||
TEXT("Friend User"),
|
||||
EHyperTwistTrainingFriendLinkState::PendingOutgoing,
|
||||
TEXT("2026-05-20T10:00:00Z")
|
||||
);
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertFriendLinkRecord(
|
||||
RepositoryState,
|
||||
FriendLinkRecord
|
||||
);
|
||||
|
||||
TArray<FHyperTwistTrainingFriendLinkRecord> FriendLinks =
|
||||
UHyperTwistTrainingRepositoryLibrary::ListFriendLinksForUser(
|
||||
RepositoryState,
|
||||
TEXT("bound5-user")
|
||||
);
|
||||
TestEqual(TEXT("One pending friend link must be stored."), FriendLinks.Num(), 1);
|
||||
if (FriendLinks.Num() != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
TestEqual(
|
||||
TEXT("The pending friend link must retain the outgoing state."),
|
||||
FriendLinks[0].State,
|
||||
EHyperTwistTrainingFriendLinkState::PendingOutgoing
|
||||
);
|
||||
|
||||
FriendLinkRecord = UHyperTwistTrainingRepositoryLibrary::ResolveFriendLinkRecordState(
|
||||
FriendLinks[0],
|
||||
EHyperTwistTrainingFriendLinkState::Active,
|
||||
TEXT("2026-05-20T10:15:00Z")
|
||||
);
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertFriendLinkRecord(
|
||||
RepositoryState,
|
||||
FriendLinkRecord
|
||||
);
|
||||
FriendLinks = UHyperTwistTrainingRepositoryLibrary::ListFriendLinksForUser(
|
||||
RepositoryState,
|
||||
TEXT("bound5-user")
|
||||
);
|
||||
|
||||
TestEqual(TEXT("Friend-link dedupe must retain one active record."), FriendLinks.Num(), 1);
|
||||
if (FriendLinks.Num() != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
TestEqual(
|
||||
TEXT("The active friend link must retain the accepted lifecycle state."),
|
||||
FriendLinks[0].State,
|
||||
EHyperTwistTrainingFriendLinkState::Active
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("The active friend link must record a responded-at timestamp."),
|
||||
!FriendLinks[0].RespondedAtUtc.IsEmpty()
|
||||
);
|
||||
|
||||
const FHyperTwistTrainingRepositoryIntegrityReport IntegrityReport =
|
||||
UHyperTwistTrainingRepositoryLibrary::DeriveRepositoryIntegrityReport(RepositoryState);
|
||||
TestEqual(TEXT("Repository integrity must retain one friend link."), IntegrityReport.TotalFriendLinkCount, 1);
|
||||
TestEqual(TEXT("Repository integrity must retain zero duplicate friend links."), IntegrityReport.DuplicateFriendLinkCount, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistCubeDeskBound5NotificationVisibilityTest,
|
||||
"HyperTwist.CleanRoom.CubeDesk.Bound5.NotificationVisibility",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistCubeDeskBound5NotificationVisibilityTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState RepositoryState;
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertNotificationRecord(
|
||||
RepositoryState,
|
||||
HyperTwistCubeDeskBound5SocialChallengeTestInternal::MakeNotificationRecord(
|
||||
TEXT("bound5-notification"),
|
||||
TEXT("bound5-user"),
|
||||
TEXT("friend-user"),
|
||||
TEXT("friend-user"),
|
||||
TEXT(""),
|
||||
EHyperTwistTrainingNotificationKind::FriendRequest,
|
||||
TEXT("2026-05-20T11:00:00Z")
|
||||
)
|
||||
);
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::MarkNotificationRead(
|
||||
RepositoryState,
|
||||
TEXT("bound5-notification"),
|
||||
TEXT("2026-05-20T11:01:00Z")
|
||||
);
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::AcknowledgeNotification(
|
||||
RepositoryState,
|
||||
TEXT("bound5-notification"),
|
||||
TEXT("2026-05-20T11:02:00Z")
|
||||
);
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::ArchiveNotification(
|
||||
RepositoryState,
|
||||
TEXT("bound5-notification"),
|
||||
TEXT("2026-05-20T11:03:00Z")
|
||||
);
|
||||
|
||||
const TArray<FHyperTwistTrainingNotificationRecord> Notifications =
|
||||
UHyperTwistTrainingRepositoryLibrary::ListNotificationsForUser(
|
||||
RepositoryState,
|
||||
TEXT("bound5-user")
|
||||
);
|
||||
TestEqual(TEXT("Exactly one notification must be retained."), Notifications.Num(), 1);
|
||||
if (Notifications.Num() != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
TestTrue(TEXT("The notification must be marked as read."), Notifications[0].bRead);
|
||||
TestTrue(TEXT("The notification must be marked as acknowledged."), Notifications[0].bAcknowledged);
|
||||
TestEqual(
|
||||
TEXT("The notification must move into the archived visibility state."),
|
||||
Notifications[0].VisibilityState,
|
||||
EHyperTwistTrainingNotificationVisibilityState::Archived
|
||||
);
|
||||
|
||||
const FHyperTwistTrainingSocialChallengeBundle SocialBundle =
|
||||
UHyperTwistTrainingRepositoryLibrary::DeriveSocialChallengeBundle(
|
||||
RepositoryState,
|
||||
TEXT("bound5-user"),
|
||||
TEXT("2026-05-20T11:04:00Z"),
|
||||
HyperTwistCubeDeskBound5SocialChallengeTestInternal::MakeVisibleEntitledGate()
|
||||
);
|
||||
TestEqual(TEXT("Archived notifications must no longer count as inbox-visible."), SocialBundle.VisibleNotificationCount, 0);
|
||||
TestEqual(TEXT("Archived notifications must not count as unread."), SocialBundle.UnreadNotificationCount, 0);
|
||||
TestEqual(TEXT("Acknowledged notifications must be counted."), SocialBundle.AcknowledgedNotificationCount, 1);
|
||||
TestEqual(TEXT("Archived notifications must be counted."), SocialBundle.ArchivedNotificationCount, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistCubeDeskBound5ChallengeLifecycleTest,
|
||||
"HyperTwist.CleanRoom.CubeDesk.Bound5.ChallengeLifecycle",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistCubeDeskBound5ChallengeLifecycleTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState RepositoryState;
|
||||
FHyperTwistTrainingChallengeRecord ChallengeRecord =
|
||||
HyperTwistCubeDeskBound5SocialChallengeTestInternal::MakeChallengeRecord(
|
||||
TEXT("bound5-challenge"),
|
||||
TEXT("bound5-user"),
|
||||
TEXT("Bound5 User"),
|
||||
TEXT("friend-user"),
|
||||
TEXT("Friend User"),
|
||||
EHyperTwistTrainingChallengeLifecycleState::Pending,
|
||||
EHyperTwistTrainingLocalMatchReadinessState::Seeded,
|
||||
TEXT("2026-05-20T12:00:00Z")
|
||||
);
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertChallengeRecord(
|
||||
RepositoryState,
|
||||
ChallengeRecord
|
||||
);
|
||||
|
||||
ChallengeRecord = UHyperTwistTrainingRepositoryLibrary::ResolveChallengeRecordState(
|
||||
ChallengeRecord,
|
||||
EHyperTwistTrainingChallengeLifecycleState::Accepted,
|
||||
EHyperTwistTrainingLocalMatchReadinessState::AwaitingAttempts,
|
||||
TEXT("2026-05-20T12:05:00Z")
|
||||
);
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertChallengeRecord(
|
||||
RepositoryState,
|
||||
ChallengeRecord
|
||||
);
|
||||
|
||||
ChallengeRecord = UHyperTwistTrainingRepositoryLibrary::ResolveChallengeRecordState(
|
||||
ChallengeRecord,
|
||||
EHyperTwistTrainingChallengeLifecycleState::Ready,
|
||||
EHyperTwistTrainingLocalMatchReadinessState::ReadyToCompare,
|
||||
TEXT("2026-05-20T12:10:00Z")
|
||||
);
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertChallengeRecord(
|
||||
RepositoryState,
|
||||
ChallengeRecord
|
||||
);
|
||||
|
||||
const TArray<FHyperTwistTrainingChallengeRecord> Challenges =
|
||||
UHyperTwistTrainingRepositoryLibrary::ListChallengesForUser(
|
||||
RepositoryState,
|
||||
TEXT("bound5-user")
|
||||
);
|
||||
TestEqual(TEXT("Exactly one challenge must be retained."), Challenges.Num(), 1);
|
||||
if (Challenges.Num() != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
TestEqual(
|
||||
TEXT("The challenge lifecycle must advance into the ready state."),
|
||||
Challenges[0].LifecycleState,
|
||||
EHyperTwistTrainingChallengeLifecycleState::Ready
|
||||
);
|
||||
TestEqual(
|
||||
TEXT("The challenge readiness must advance into the ready-to-compare state."),
|
||||
Challenges[0].MatchReadinessState,
|
||||
EHyperTwistTrainingLocalMatchReadinessState::ReadyToCompare
|
||||
);
|
||||
TestTrue(TEXT("Accepted challenges must retain a responded-at timestamp."), !Challenges[0].RespondedAtUtc.IsEmpty());
|
||||
TestTrue(TEXT("Ready challenges must retain a ready-at timestamp."), !Challenges[0].ReadyAtUtc.IsEmpty());
|
||||
|
||||
const FHyperTwistTrainingSocialChallengeBundle SocialBundle =
|
||||
UHyperTwistTrainingRepositoryLibrary::DeriveSocialChallengeBundle(
|
||||
RepositoryState,
|
||||
TEXT("bound5-user"),
|
||||
TEXT("2026-05-20T12:11:00Z"),
|
||||
HyperTwistCubeDeskBound5SocialChallengeTestInternal::MakeVisibleEntitledGate()
|
||||
);
|
||||
TestEqual(TEXT("The ready challenge must count as active."), SocialBundle.ActiveChallengeCount, 1);
|
||||
TestEqual(TEXT("The ready challenge must count as ready to compare."), SocialBundle.ReadyChallengeCount, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistCubeDeskBound5ComparisonProjectionTest,
|
||||
"HyperTwist.CleanRoom.CubeDesk.Bound5.ComparisonProjection",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistCubeDeskBound5ComparisonProjectionTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
UGameInstance* GameInstance = NewObject<UGameInstance>(GetTransientPackage());
|
||||
TestNotNull(TEXT("A game instance must exist for the Bound 5 subsystem test."), GameInstance);
|
||||
if (GameInstance == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
|
||||
TestNotNull(TEXT("The training subsystem must be created."), TrainingSubsystem);
|
||||
if (TrainingSubsystem == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
|
||||
HyperTwistCubeDeskBound5SocialChallengeTestInternal::MakeDeck(TEXT("deck/hypercube-3333")),
|
||||
TEXT("bound5-user"),
|
||||
TEXT("bound5-social-session"),
|
||||
EHyperTwistTrainingDeliveryMode::Timer
|
||||
);
|
||||
TestTrue(TEXT("A social-challenge repository view must start from a valid run."), RunState.IsStructurallyValid());
|
||||
if (!RunState.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool bSavedFriendLink = TrainingSubsystem->SaveFriendLinkRecord(
|
||||
HyperTwistCubeDeskBound5SocialChallengeTestInternal::MakeFriendLinkRecord(
|
||||
TEXT("bound5-user"),
|
||||
TEXT("friend-user"),
|
||||
TEXT("Friend User"),
|
||||
EHyperTwistTrainingFriendLinkState::Active,
|
||||
TEXT("2026-05-20T13:00:00Z")
|
||||
)
|
||||
);
|
||||
TestTrue(TEXT("The subsystem must persist local friend-link state."), bSavedFriendLink);
|
||||
|
||||
const bool bSavedNotification = TrainingSubsystem->SaveNotificationRecord(
|
||||
HyperTwistCubeDeskBound5SocialChallengeTestInternal::MakeNotificationRecord(
|
||||
TEXT("bound5-challenge-notification"),
|
||||
TEXT("bound5-user"),
|
||||
TEXT("friend-user"),
|
||||
TEXT("friend-user"),
|
||||
TEXT("bound5-comparison-challenge"),
|
||||
EHyperTwistTrainingNotificationKind::ChallengeReady,
|
||||
TEXT("2026-05-20T13:01:00Z")
|
||||
)
|
||||
);
|
||||
TestTrue(TEXT("The subsystem must persist local notification state."), bSavedNotification);
|
||||
|
||||
const bool bSavedChallenge = TrainingSubsystem->SaveChallengeRecord(
|
||||
HyperTwistCubeDeskBound5SocialChallengeTestInternal::MakeChallengeRecord(
|
||||
TEXT("bound5-comparison-challenge"),
|
||||
TEXT("bound5-user"),
|
||||
TEXT("Bound5 User"),
|
||||
TEXT("friend-user"),
|
||||
TEXT("Friend User"),
|
||||
EHyperTwistTrainingChallengeLifecycleState::Ready,
|
||||
EHyperTwistTrainingLocalMatchReadinessState::ReadyToCompare,
|
||||
TEXT("2026-05-20T13:02:00Z")
|
||||
)
|
||||
);
|
||||
TestTrue(TEXT("The subsystem must persist local challenge state."), bSavedChallenge);
|
||||
|
||||
const FHyperTwistTrainingSocialChallengeBundle SocialBundle =
|
||||
TrainingSubsystem->GetActiveSocialChallengeBundle();
|
||||
TestTrue(TEXT("The subsystem must materialize a structurally valid social bundle."), SocialBundle.IsStructurallyValid());
|
||||
TestEqual(TEXT("The subsystem must surface one stored friend link."), SocialBundle.FriendLinks.Num(), 1);
|
||||
TestEqual(TEXT("The subsystem must surface one stored notification."), SocialBundle.Notifications.Num(), 1);
|
||||
TestEqual(TEXT("The subsystem must surface one stored challenge."), SocialBundle.Challenges.Num(), 1);
|
||||
TestEqual(TEXT("The subsystem must surface one comparison bundle."), SocialBundle.ComparisonBundles.Num(), 1);
|
||||
if (SocialBundle.ComparisonBundles.Num() != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingChallengeComparisonBundle& ComparisonBundle = SocialBundle.ComparisonBundles[0];
|
||||
TestTrue(TEXT("The comparison bundle must resolve as structurally valid."), ComparisonBundle.IsStructurallyValid());
|
||||
TestEqual(TEXT("The comparison bundle must retain the challenge id."), ComparisonBundle.ChallengeId, TEXT("bound5-comparison-challenge"));
|
||||
TestEqual(TEXT("The comparison bundle must retain two compared entries."), ComparisonBundle.Entries.Num(), 2);
|
||||
TestEqual(TEXT("The comparison bundle must retain the landed event title."), ComparisonBundle.EventTitle, TEXT("3x3x3x3 Single"));
|
||||
TestEqual(TEXT("The comparison bundle must retain the landed metric label."), ComparisonBundle.MetricLabel, TEXT("Time"));
|
||||
TestTrue(TEXT("The comparison bundle must report ready-to-compare state."), ComparisonBundle.bReadyToCompare);
|
||||
|
||||
const FHyperTwistTrainingChallengeComparisonEntry* CurrentUserEntry =
|
||||
HyperTwistCubeDeskBound5SocialChallengeTestInternal::FindCurrentUserEntry(ComparisonBundle);
|
||||
TestNotNull(TEXT("One comparison entry must be marked as the current user."), CurrentUserEntry);
|
||||
if (CurrentUserEntry == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TestEqual(TEXT("The current-user comparison entry must retain the user id."), CurrentUserEntry->UserId, TEXT("bound5-user"));
|
||||
TestEqual(TEXT("The current-user comparison entry must retain the local metric value."), CurrentUserEntry->MetricPrimaryValue, TEXT("12.34"));
|
||||
TestEqual(TEXT("The current-user comparison entry must retain the program badge."), CurrentUserEntry->ProgramLabel, TEXT("HSC"));
|
||||
|
||||
const FHyperTwistTrainingRepositoryRoundTripVerification Verification =
|
||||
UHyperTwistTrainingPersistenceLibrary::VerifyTrainingRepositoryStateRoundTrip(
|
||||
TrainingSubsystem->GetTrainingRepositoryState(),
|
||||
TEXT("Automation/HyperTwistCubeDeskBound5RepositoryRoundTrip.json")
|
||||
);
|
||||
TestTrue(TEXT("Repository roundtrip verification must pass for Bound 5 state."), Verification.bDidRoundTripPass);
|
||||
TestEqual(TEXT("Roundtrip verification must retain one friend link."), Verification.LoadedFriendLinkCount, 1);
|
||||
TestEqual(TEXT("Roundtrip verification must retain one notification."), Verification.LoadedNotificationCount, 1);
|
||||
TestEqual(TEXT("Roundtrip verification must retain one challenge."), Verification.LoadedChallengeCount, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Reference in a new issue