diff --git a/rcheevos b/rcheevos
index e3f7b7d20..f06b14aa4 160000
--- a/rcheevos
+++ b/rcheevos
@@ -1 +1 @@
-Subproject commit e3f7b7d20c9253ae9340cd433199b838d061716c
+Subproject commit f06b14aa41fa1ed41e0680c319314742c3cf1ebe
diff --git a/src/RA_Integration.vcxproj b/src/RA_Integration.vcxproj
index f2cb0c821..71756d265 100644
--- a/src/RA_Integration.vcxproj
+++ b/src/RA_Integration.vcxproj
@@ -197,7 +197,6 @@
-
diff --git a/src/RA_Integration.vcxproj.filters b/src/RA_Integration.vcxproj.filters
index b55ed28de..f2cfdcc79 100644
--- a/src/RA_Integration.vcxproj.filters
+++ b/src/RA_Integration.vcxproj.filters
@@ -794,9 +794,6 @@
UI\ViewModels
-
- API
-
UI\ViewModels
diff --git a/src/api/ApiCall.cpp b/src/api/ApiCall.cpp
index c4b402e17..0141c0f58 100644
--- a/src/api/ApiCall.cpp
+++ b/src/api/ApiCall.cpp
@@ -15,7 +15,6 @@ static ra::api::IServer& Server()
ResolveHash::Response ResolveHash::Request::Call() const { return Server().ResolveHash(*this); }
UpdateAchievement::Response UpdateAchievement::Request::Call() const { return Server().UpdateAchievement(*this); }
FetchAchievementInfo::Response FetchAchievementInfo::Request::Call() const { return Server().FetchAchievementInfo(*this); }
-UpdateLeaderboard::Response UpdateLeaderboard::Request::Call() const { return Server().UpdateLeaderboard(*this); }
FetchLeaderboardInfo::Response FetchLeaderboardInfo::Request::Call() const { return Server().FetchLeaderboardInfo(*this); }
UpdateRichPresence::Response UpdateRichPresence::Request::Call() const { return Server().UpdateRichPresence(*this); }
UploadBadge::Response UploadBadge::Request::Call() const { return Server().UploadBadge(*this); }
diff --git a/src/api/IServer.hh b/src/api/IServer.hh
index e31621184..95424c87d 100644
--- a/src/api/IServer.hh
+++ b/src/api/IServer.hh
@@ -6,7 +6,6 @@
#include "api/FetchLeaderboardInfo.hh"
#include "api/ResolveHash.hh"
#include "api/UpdateAchievement.hh"
-#include "api/UpdateLeaderboard.hh"
#include "api/UpdateRichPresence.hh"
#include "api/UploadBadge.hh"
@@ -22,7 +21,6 @@ public:
virtual ResolveHash::Response ResolveHash(const ResolveHash::Request& request) = 0;
virtual UpdateAchievement::Response UpdateAchievement(const UpdateAchievement::Request& request) = 0;
virtual FetchAchievementInfo::Response FetchAchievementInfo(const FetchAchievementInfo::Request& request) = 0;
- virtual UpdateLeaderboard::Response UpdateLeaderboard(const UpdateLeaderboard::Request& request) = 0;
virtual FetchLeaderboardInfo::Response FetchLeaderboardInfo(const FetchLeaderboardInfo::Request& request) = 0;
virtual UpdateRichPresence::Response UpdateRichPresence(const UpdateRichPresence::Request& request) = 0;
diff --git a/src/api/UpdateLeaderboard.hh b/src/api/UpdateLeaderboard.hh
deleted file mode 100644
index e0bd3a652..000000000
--- a/src/api/UpdateLeaderboard.hh
+++ /dev/null
@@ -1,49 +0,0 @@
-#ifndef RA_API_UPDATE_LEADERBOARD_HH
-#define RA_API_UPDATE_LEADERBOARD_HH
-#pragma once
-
-#include "ApiCall.hh"
-
-#include "data\Value.hh"
-
-namespace ra {
-namespace api {
-
-class UpdateLeaderboard
-{
-public:
- static constexpr const char* const Name() noexcept { return "UpdateLeaderboard"; }
-
- struct Response : ApiResponseBase
- {
- unsigned int LeaderboardId{ 0U };
- };
-
- struct Request : ApiRequestBase
- {
- unsigned int LeaderboardId{ 0U };
- unsigned int GameId{ 0U };
- std::wstring Title;
- std::wstring Description;
- std::string StartTrigger;
- std::string SubmitTrigger;
- std::string CancelTrigger;
- std::string ValueDefinition;
- ra::data::Value::Format Format{ ra::data::Value::Format::Value };
- bool LowerIsBetter{ false };
-
- using Callback = std::function;
-
- Response Call() const;
-
- void CallAsync(Callback&& callback) const
- {
- ApiRequestBase::CallAsync(*this, std::move(callback));
- }
- };
-};
-
-} // namespace api
-} // namespace ra
-
-#endif // !RA_API_UPDATE_LEADERBOARD_HH
diff --git a/src/api/impl/ConnectedServer.cpp b/src/api/impl/ConnectedServer.cpp
index 0a0ec043e..4d0361804 100644
--- a/src/api/impl/ConnectedServer.cpp
+++ b/src/api/impl/ConnectedServer.cpp
@@ -531,63 +531,6 @@ FetchAchievementInfo::Response ConnectedServer::FetchAchievementInfo(const Fetch
return response;
}
-UpdateLeaderboard::Response ConnectedServer::UpdateLeaderboard(const UpdateLeaderboard::Request& request)
-{
- UpdateLeaderboard::Response response;
-
- rc_api_update_leaderboard_request_t api_params;
- memset(&api_params, 0, sizeof(api_params));
-
- const auto& pUserContext = ra::services::ServiceLocator::Get();
- api_params.username = pUserContext.GetUsername().c_str();
- api_params.api_token = pUserContext.GetApiToken().c_str();
-
- const std::string sTitle = ra::util::String::Narrow(request.Title);
- const std::string sDescription = ra::util::String::Narrow(request.Description);
-
- api_params.leaderboard_id = request.LeaderboardId;
- api_params.game_id = request.GameId;
- api_params.title = sTitle.c_str();
- api_params.description = sDescription.c_str();
- api_params.start_trigger = request.StartTrigger.c_str();
- api_params.submit_trigger = request.SubmitTrigger.c_str();
- api_params.cancel_trigger = request.CancelTrigger.c_str();
- api_params.value_definition = request.ValueDefinition.c_str();
- api_params.lower_is_better = request.LowerIsBetter ? 1 : 0;
- api_params.format = ra::data::Value::FormatToServerEnum(request.Format);
-
- rc_api_request_t api_request;
- const int result = rc_api_init_update_leaderboard_request(&api_request, &api_params);
- if (result == RC_OK)
- {
- ra::services::Http::Response httpResponse;
- if (DoRequest(api_request, UpdateLeaderboard::Name(), httpResponse, response))
- {
- rc_api_update_leaderboard_response_t api_response;
- rc_api_server_response_t server_response;
- HttpResponseToServerResponse(httpResponse, &server_response);
-
- const auto nResult = rc_api_process_update_leaderboard_server_response(&api_response, &server_response);
-
- if (ValidateResponse(nResult, api_response.response, UpdateLeaderboard::Name(), httpResponse.StatusCode(), response))
- {
- response.Result = ApiResult::Success;
- response.LeaderboardId = api_response.leaderboard_id;
- }
-
- rc_api_destroy_update_leaderboard_response(&api_response);
- }
- }
- else
- {
- response.Result = ApiResult::Failed;
- response.ErrorMessage = rc_error_str(result);
- }
-
- rc_api_destroy_request(&api_request);
- return response;
-}
-
FetchLeaderboardInfo::Response ConnectedServer::FetchLeaderboardInfo(const FetchLeaderboardInfo::Request& request)
{
FetchLeaderboardInfo::Response response;
diff --git a/src/api/impl/ConnectedServer.hh b/src/api/impl/ConnectedServer.hh
index 52acd6091..b71026cf8 100644
--- a/src/api/impl/ConnectedServer.hh
+++ b/src/api/impl/ConnectedServer.hh
@@ -18,7 +18,6 @@ public:
ResolveHash::Response ResolveHash(const ResolveHash::Request& request) override;
UpdateAchievement::Response UpdateAchievement(const UpdateAchievement::Request& request) override;
FetchAchievementInfo::Response FetchAchievementInfo(const FetchAchievementInfo::Request& request) override;
- UpdateLeaderboard::Response UpdateLeaderboard(const UpdateLeaderboard::Request& request) override;
FetchLeaderboardInfo::Response FetchLeaderboardInfo(const FetchLeaderboardInfo::Request& request) override;
UpdateRichPresence::Response UpdateRichPresence(const UpdateRichPresence::Request& request) override;
UploadBadge::Response UploadBadge(const UploadBadge::Request& request) override;
diff --git a/src/api/impl/ServerBase.hh b/src/api/impl/ServerBase.hh
index bb92592ac..7eb59d378 100644
--- a/src/api/impl/ServerBase.hh
+++ b/src/api/impl/ServerBase.hh
@@ -32,11 +32,6 @@ public:
return UnsupportedApi(FetchAchievementInfo::Name());
}
- UpdateLeaderboard::Response UpdateLeaderboard(const UpdateLeaderboard::Request&) override
- {
- return UnsupportedApi(UpdateLeaderboard::Name());
- }
-
FetchLeaderboardInfo::Response FetchLeaderboardInfo(const FetchLeaderboardInfo::Request&) override
{
return UnsupportedApi(FetchLeaderboardInfo::Name());
diff --git a/src/devkit/data/models/LeaderboardModel.cpp b/src/devkit/data/models/LeaderboardModel.cpp
index daaca8732..96c2ce26c 100644
--- a/src/devkit/data/models/LeaderboardModel.cpp
+++ b/src/devkit/data/models/LeaderboardModel.cpp
@@ -318,6 +318,21 @@ void LeaderboardModel::SyncTrackerToRuntime() const
}
}
+void LeaderboardModel::SyncCategoryToRuntime() const
+{
+ switch (GetCategory())
+ {
+ case ra::data::models::AssetCategory::Promoted:
+ case ra::data::models::AssetCategory::Local:
+ m_pLeaderboardInfo->public_.category = RC_CLIENT_LEADERBOARD_CATEGORY_PROMOTED;
+ break;
+
+ default:
+ m_pLeaderboardInfo->public_.category = RC_CLIENT_LEADERBOARD_CATEGORY_UNPROMOTED;
+ break;
+ }
+}
+
void LeaderboardModel::SyncDefinitionToRuntime()
{
Expects(m_pLeaderboardInfo != nullptr);
@@ -477,7 +492,7 @@ void LeaderboardModel::InitializeFromPublishedLeaderboard(
SetID(pLeaderboard.public_.id);
SetName(ra::util::String::Widen(pLeaderboard.public_.title));
SetDescription(ra::util::String::Widen(pLeaderboard.public_.description));
- SetCategory(AssetCategory::Promoted);
+ SetCategory(pLeaderboard.public_.category == RC_CLIENT_LEADERBOARD_CATEGORY_PROMOTED ? AssetCategory::Promoted : AssetCategory::Unpromoted);
SetValueFormat(Value::FormatFromRcheevosFormat(pLeaderboard.format));
SetLowerIsBetter(pLeaderboard.public_.lower_is_better);
SetHidden(pLeaderboard.hidden);
@@ -535,6 +550,7 @@ void LeaderboardModel::SyncToLocalLeaderboardInfo()
SyncTitleToRuntime();
SyncDescriptionToRuntime();
SyncDefinitionToRuntime();
+ SyncCategoryToRuntime();
SyncValueFormatToRuntime();
}
diff --git a/src/devkit/data/models/LeaderboardModel.hh b/src/devkit/data/models/LeaderboardModel.hh
index 4773d53d2..c3c84249e 100644
--- a/src/devkit/data/models/LeaderboardModel.hh
+++ b/src/devkit/data/models/LeaderboardModel.hh
@@ -233,6 +233,7 @@ private:
void SyncStateToRuntime(AssetState nNewState) const;
void SyncValueFormatToRuntime() const;
void SyncTrackerToRuntime() const;
+ void SyncCategoryToRuntime() const;
void SyncDefinitionToRuntime();
void SyncStateFromRuntime(uint8_t nState);
diff --git a/src/ui/viewmodels/AssetListViewModel.cpp b/src/ui/viewmodels/AssetListViewModel.cpp
index 9f94b554b..fae502649 100644
--- a/src/ui/viewmodels/AssetListViewModel.cpp
+++ b/src/ui/viewmodels/AssetListViewModel.cpp
@@ -1250,9 +1250,6 @@ void AssetListViewModel::SaveSelected()
{
switch (pAsset->GetType())
{
- case ra::data::models::AssetType::Leaderboard:
- ra::ui::viewmodels::MessageBoxViewModel::ShowWarningMessage(L"Leaderboards cannot be demoted.");
- return;
case ra::data::models::AssetType::RichPresence:
ra::ui::viewmodels::MessageBoxViewModel::ShowWarningMessage(L"Rich Presence cannot be demoted.");
return;
diff --git a/src/ui/viewmodels/AssetUploadViewModel.cpp b/src/ui/viewmodels/AssetUploadViewModel.cpp
index 65a486f6f..4a99c9eea 100644
--- a/src/ui/viewmodels/AssetUploadViewModel.cpp
+++ b/src/ui/viewmodels/AssetUploadViewModel.cpp
@@ -1,7 +1,6 @@
#include "AssetUploadViewModel.hh"
#include "api\UpdateAchievement.hh"
-#include "api\UpdateLeaderboard.hh"
#include "api\UpdateRichPresence.hh"
#include "api\UploadBadge.hh"
@@ -21,6 +20,7 @@
#include
#include
+#include
namespace ra {
namespace ui {
@@ -346,51 +346,108 @@ void AssetUploadViewModel::UploadAchievement(ra::data::models::AchievementModel&
void AssetUploadViewModel::UploadLeaderboard(ra::data::models::LeaderboardModel& pLeaderboard)
{
const auto& pGameContext = ra::services::ServiceLocator::Get();
+ std::string sNarrowTitle = ra::util::String::Narrow(pLeaderboard.GetName());
+ std::string sNarrowDescription = ra::util::String::Narrow(pLeaderboard.GetDescription());
- ra::api::UpdateLeaderboard::Request request;
- request.GameId = pGameContext.GetGameId(pLeaderboard.GetSubsetID());
- request.Title = pLeaderboard.GetName();
- request.Description = pLeaderboard.GetDescription();
- request.StartTrigger = pLeaderboard.GetStartTrigger();
- request.SubmitTrigger = pLeaderboard.GetSubmitTrigger();
- request.CancelTrigger = pLeaderboard.GetCancelTrigger();
- request.ValueDefinition = pLeaderboard.GetValueDefinition();
- request.Format = pLeaderboard.GetValueFormat();
- request.LowerIsBetter = pLeaderboard.IsLowerBetter();
+ // construct the API request
+ rc_api_update_leaderboard_request_t api_params;
+ memset(&api_params, 0, sizeof(api_params));
+ const auto& pUserContext = ra::services::ServiceLocator::Get();
+ api_params.username = pUserContext.GetUsername().c_str();
+ api_params.api_token = pUserContext.GetApiToken().c_str();
+ api_params.game_id = pGameContext.GetGameId(pLeaderboard.GetSubsetID()); // TODO: switch to achievement_set_id
+ api_params.title = sNarrowTitle.c_str();
+ api_params.description = sNarrowDescription.c_str();
+ api_params.start_trigger = pLeaderboard.GetStartTrigger().c_str();
+ api_params.submit_trigger = pLeaderboard.GetSubmitTrigger().c_str();
+ api_params.cancel_trigger = pLeaderboard.GetCancelTrigger().c_str();
+ api_params.value_definition = pLeaderboard.GetValueDefinition().c_str();
+ api_params.format = ra::data::Value::FormatToServerEnum(pLeaderboard.GetValueFormat());
+ api_params.lower_is_better = pLeaderboard.IsLowerBetter() ? 1 : 0;
+
+ switch (pLeaderboard.GetCategory())
+ {
+ case ra::data::models::AssetCategory::Local:
+ // new leaderboard, submit to unpromoted:
+ api_params.state = RC_LEADERBOARD_STATE_UNPROMOTED;
+ break;
- if (pLeaderboard.GetCategory() != ra::data::models::AssetCategory::Local)
- request.LeaderboardId = pLeaderboard.GetID();
+ case ra::data::models::AssetCategory::Promoted:
+ // promoted leaderboard
+ api_params.leaderboard_id = pLeaderboard.GetID();
+ api_params.state = RC_LEADERBOARD_STATE_ACTIVE;
+ break;
- const auto& response = request.Call();
+ default:
+ // unpromoted leaderboard
+ api_params.leaderboard_id = pLeaderboard.GetID();
+ api_params.state = RC_LEADERBOARD_STATE_UNPROMOTED;
+ break;
+ }
- // update the leaderboard model
- if (response.Succeeded())
+ auto& pClient = ra::services::ServiceLocator::Get();
+ rc_api_request_t api_request;
+ auto nResult = rc_api_init_update_leaderboard_request_hosted(&api_request, &api_params, pClient.GetHost());
+ std::string sErrorMessage = rc_error_str(nResult);
+
+ if (nResult == RC_OK)
{
- if (pLeaderboard.GetCategory() == ra::data::models::AssetCategory::Local)
+ bool bRetry = false;
+ std::string sResponseBuffer;
+
+ do
{
- pLeaderboard.SetCategory(ra::data::models::AssetCategory::Promoted);
- pLeaderboard.SetID(response.LeaderboardId);
- }
+ bRetry = false;
- pLeaderboard.UpdateLocalCheckpoint();
- pLeaderboard.UpdateServerCheckpoint();
- }
- else if (response.Result == ra::api::ApiResult::Incomplete)
- {
- Rest();
- UploadLeaderboard(pLeaderboard);
- return;
+ // send the API request
+ rc_api_server_response_t api_response;
+ pClient.SendRequest(api_request, api_response, sResponseBuffer);
+
+ // process the response
+ rc_api_update_leaderboard_response_t response;
+ nResult = rc_api_process_update_leaderboard_server_response(&response, &api_response);
+ if (nResult == RC_OK)
+ {
+ if (pLeaderboard.GetCategory() == ra::data::models::AssetCategory::Local)
+ {
+ pLeaderboard.SetCategory(ra::data::models::AssetCategory::Unpromoted);
+ pLeaderboard.SetID(response.leaderboard_id);
+ }
+
+ pLeaderboard.UpdateLocalCheckpoint();
+ pLeaderboard.UpdateServerCheckpoint();
+ }
+ else
+ {
+ bRetry = rc_client_should_retry(&api_response);
+ if (!bRetry)
+ {
+ if (response.response.error_message)
+ sErrorMessage = response.response.error_message;
+ else
+ sErrorMessage = rc_error_str(nResult);
+ }
+ }
+
+ rc_api_destroy_update_leaderboard_response(&response);
+
+ if (!bRetry)
+ break;
+
+ Rest();
+ } while (true);
}
- // update the queue
+ rc_api_destroy_request(&api_request);
+
std::lock_guard pLock(m_pMutex);
for (auto& pScan : m_vUploadQueue)
{
if (pScan.pAsset == &pLeaderboard)
{
- pScan.sErrorMessage = response.ErrorMessage;
+ pScan.sErrorMessage = sErrorMessage;
- if (response.ErrorMessage == "Invalid state")
+ if (nResult == RC_INVALID_STATE)
{
// generic API failure. Try to guess what went wrong
if (pLeaderboard.GetName().empty())
@@ -407,7 +464,7 @@ void AssetUploadViewModel::UploadLeaderboard(ra::data::models::LeaderboardModel&
pScan.sErrorMessage = "At least one value condition is required";
}
- pScan.nState = response.Succeeded() ? UploadState::Success : UploadState::Failed;
+ pScan.nState = (nResult == RC_OK) ? UploadState::Success : UploadState::Failed;
break;
}
}
@@ -587,8 +644,7 @@ void AssetUploadViewModel::UploadMemoryNotes(ra::data::models::MemoryNotesModel&
}
else
{
- bRetry = api_response.http_status_code == ra::etoi(ra::services::Http::StatusCode::TooManyRequests)
- || api_response.http_status_code == RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR;
+ bRetry = rc_client_should_retry(&api_response);
if (!bRetry)
{
if (response.response.error_message)
diff --git a/tests/data/context/GameContext_Tests.cpp b/tests/data/context/GameContext_Tests.cpp
index ada3a869d..5714dfaa0 100644
--- a/tests/data/context/GameContext_Tests.cpp
+++ b/tests/data/context/GameContext_Tests.cpp
@@ -151,7 +151,7 @@ TEST_CLASS(GameContext_Tests)
mockAchievementRuntime.MockUser("Username", "ApiToken");
mockRcClient.MockResponse(
- "r=achievementsets&u=Username&t=ApiToken&m=" + sHash,
+ "r=achievementsets&u=Username&t=ApiToken&m=" + sHash + "&v=2",
"{\"Success\":true,"
"\"GameId\":" + std::to_string(nGameID) + ","
"\"Title\":\"GameTitle\","
@@ -917,7 +917,7 @@ TEST_CLASS(GameContext_Tests)
GameContextHarness game;
game.MockLoadGameAPIs(1U, "0123456789abcdeffedcba987654321");
game.mockRcClient.MockResponse(
- "r=achievementsets&u=Username&t=ApiToken&m=0123456789abcdeffedcba987654321",
+ "r=achievementsets&u=Username&t=ApiToken&m=0123456789abcdeffedcba987654321&v=2",
"{\"Success\":true,"
"\"GameId\":1,"
"\"Title\":\"GameTitle\","
@@ -1315,7 +1315,7 @@ TEST_CLASS(GameContext_Tests)
game.MockLoadGameAPIs(1U, "0123456789abcdeffedcba987654321");
bool bBeforeResponseCalled = false;
- game.mockRcClient.OnBeforeResponse("r=achievementsets&u=Username&t=ApiToken&m=0123456789abcdeffedcba987654321",
+ game.mockRcClient.OnBeforeResponse("r=achievementsets&u=Username&t=ApiToken&m=0123456789abcdeffedcba987654321&v=2",
[&game, &bBeforeResponseCalled]() {
bBeforeResponseCalled = true;
Assert::IsTrue(game.mockAchievementRuntime.IsPaused());
@@ -1333,7 +1333,7 @@ TEST_CLASS(GameContext_Tests)
game.MockLoadGameAPIs(1U, "0123456789abcdeffedcba987654321");
bool bBeforeResponseCalled = false;
- game.mockRcClient.OnBeforeResponse("r=achievementsets&u=Username&t=ApiToken&m=0123456789abcdeffedcba987654321",
+ game.mockRcClient.OnBeforeResponse("r=achievementsets&u=Username&t=ApiToken&m=0123456789abcdeffedcba987654321&v=2",
[&game, &bBeforeResponseCalled]() {
bBeforeResponseCalled = true;
Assert::IsTrue(game.mockAchievementRuntime.IsPaused());
diff --git a/tests/devkit/context/mocks/MockRcClient.cpp b/tests/devkit/context/mocks/MockRcClient.cpp
index 58c09607b..c6a0b1459 100644
--- a/tests/devkit/context/mocks/MockRcClient.cpp
+++ b/tests/devkit/context/mocks/MockRcClient.cpp
@@ -94,9 +94,12 @@ void MockRcClient::SendRequest(const rc_api_request_t& pRequest, rc_api_server_r
}
}
- pServerResponse.http_status_code = 504; // Gateway Timeout (upstream server took too long to response to intermediary proxy)
- pServerResponse.body = "";
- pServerResponse.body_length = 0;
+ // 410 Gone indicates the resource is not available.
+ // More importantly, it's not classified as a retryable error, so a test that's not using the correct
+ // request string won't retry infinitely.
+ pServerResponse.http_status_code = 410;
+ pServerResponse.body = "Gone";
+ pServerResponse.body_length = 4;
}
void MockRcClient::MockResponse(const std::string& sRequestParams, const std::string& sResponseBody, int nHttpStatusCode)
diff --git a/tests/mocks/MockAchievementRuntime.cpp b/tests/mocks/MockAchievementRuntime.cpp
index 42e55a308..851a1a45d 100644
--- a/tests/mocks/MockAchievementRuntime.cpp
+++ b/tests/mocks/MockAchievementRuntime.cpp
@@ -262,6 +262,7 @@ static rc_client_leaderboard_info_t* AddLeaderboard(const rc_client_t* client, r
const std::string sGeneratedDescripton = ra::util::String::Printf("Description %u", nId);
leaderboard->public_.description = rc_buffer_strcpy(&game->buffer, sGeneratedDescripton.c_str());
+ leaderboard->public_.category = RC_CLIENT_LEADERBOARD_CATEGORY_PROMOTED;
leaderboard->public_.state = static_cast(rc_client_get_hardcore_enabled(client) ?
RC_CLIENT_LEADERBOARD_STATE_ACTIVE : RC_CLIENT_LEADERBOARD_STATE_INACTIVE);
diff --git a/tests/mocks/MockServer.hh b/tests/mocks/MockServer.hh
index a2cdce642..fb7ec00e1 100644
--- a/tests/mocks/MockServer.hh
+++ b/tests/mocks/MockServer.hh
@@ -73,11 +73,6 @@ public:
return HandleRequest(request);
}
- UpdateLeaderboard::Response UpdateLeaderboard(const UpdateLeaderboard::Request& request) override
- {
- return HandleRequest(request);
- }
-
FetchLeaderboardInfo::Response FetchLeaderboardInfo(const FetchLeaderboardInfo::Request& request) override
{
return HandleRequest(request);
diff --git a/tests/ui/viewmodels/AssetListViewModel_Tests.cpp b/tests/ui/viewmodels/AssetListViewModel_Tests.cpp
index b731c2ec4..8b0fb904b 100644
--- a/tests/ui/viewmodels/AssetListViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/AssetListViewModel_Tests.cpp
@@ -3221,8 +3221,10 @@ TEST_CLASS(AssetListViewModel_Tests)
vmAssetList.mockDesktop.ExpectWindow([&bDialogSeen](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox)
{
bDialogSeen = true;
- Assert::AreEqual(std::wstring(L"Leaderboards cannot be demoted."), vmMessageBox.GetMessage());
- return DialogResult::OK;
+ Assert::AreEqual(std::wstring(L"Are you sure you want to demote 2 items?"), vmMessageBox.GetHeader());
+ Assert::AreEqual(std::wstring(L"Unpromoted items can no longer be earned by players."), vmMessageBox.GetMessage());
+ Assert::AreEqual(ra::ui::viewmodels::MessageBoxViewModel::Buttons::YesNo, vmMessageBox.GetButtons());
+ return DialogResult::Yes;
});
const auto* pItem1 = vmAssetList.mockGameContext.Assets().GetItemAt(0);
@@ -3242,13 +3244,13 @@ TEST_CLASS(AssetListViewModel_Tests)
Assert::IsTrue(bDialogSeen);
Assert::AreEqual(AssetChanges::None, pItem1->GetChanges());
- Assert::AreEqual(AssetCategory::Promoted, pItem1->GetCategory());
+ Assert::AreEqual(AssetCategory::Unpromoted, pItem1->GetCategory());
Assert::AreEqual(AssetChanges::None, pItem2->GetChanges());
- Assert::AreEqual(AssetCategory::Promoted, pItem2->GetCategory());
+ Assert::AreEqual(AssetCategory::Unpromoted, pItem2->GetCategory());
- // item will have been moved to Promoted, so nothing will be selected
+ // item will have been moved to Unpromoted, so nothing will be selected
vmAssetList.ForceUpdateButtons();
- vmAssetList.AssertButtonState(SaveButtonState::Demote);
+ vmAssetList.AssertButtonState(SaveButtonState::SaveAllDisabled);
}
TEST_METHOD(TestSaveSelectedPublishLocal)
diff --git a/tests/ui/viewmodels/AssetUploadViewModel_Tests.cpp b/tests/ui/viewmodels/AssetUploadViewModel_Tests.cpp
index eca6103d1..ab3001947 100644
--- a/tests/ui/viewmodels/AssetUploadViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/AssetUploadViewModel_Tests.cpp
@@ -1076,39 +1076,24 @@ TEST_CLASS(AssetUploadViewModel_Tests)
TEST_METHOD(TestSingleLocalLeaderboard)
{
AssetUploadViewModelHarness vmUpload;
+ vmUpload.mockUserContext.Initialize("User", "APITOKEN");
auto& pLeaderboard = vmUpload.AddLeaderboard(AssetCategory::Local, L"Title1", L"Desc1", "0xH1234=1", "0xH1234=2", "0xH1234=3", "0xH2345", ra::data::Value::Format::Score);
Assert::AreEqual(AssetChanges::Unpublished, pLeaderboard.GetChanges());
vmUpload.QueueAsset(pLeaderboard);
Assert::AreEqual({ 1U }, vmUpload.TaskCount());
- bool bApiCalled = false;
- vmUpload.mockServer.HandleRequest([&bApiCalled]
- (const ra::api::UpdateLeaderboard::Request& pRequest, ra::api::UpdateLeaderboard::Response& pResponse)
- {
- bApiCalled = true;
- Assert::AreEqual(AssetUploadViewModelHarness::GameId, pRequest.GameId);
- Assert::AreEqual(std::wstring(L"Title1"), pRequest.Title);
- Assert::AreEqual(std::wstring(L"Desc1"), pRequest.Description);
- Assert::AreEqual(std::string("0xH1234=1"), pRequest.StartTrigger);
- Assert::AreEqual(std::string("0xH1234=2"), pRequest.SubmitTrigger);
- Assert::AreEqual(std::string("0xH1234=3"), pRequest.CancelTrigger);
- Assert::AreEqual(std::string("0xH2345"), pRequest.ValueDefinition);
- Assert::AreEqual(ra::data::Value::Format::Score, pRequest.Format);
- Assert::IsFalse(pRequest.LowerIsBetter);
- Assert::AreEqual(0U, pRequest.LeaderboardId);
-
- pResponse.LeaderboardId = 7716U;
- pResponse.Result = ra::api::ApiResult::Success;
- return true;
- });
+ vmUpload.mockRcClient.MockResponse(
+ "r=uploadleaderboard&u=User&t=APITOKEN&g=22&n=Title1&d=Desc1&s=0xH1234%3d1&b=0xH1234%3d2&c=0xH1234%3d3&l=0xH2345&w=0&f=SCORE&m=unpromoted&h=b9e48151adc764317296ac777ab550f1",
+ "{\"Success\":\"true\",\"LeaderboardID\":7716}"
+ );
vmUpload.DoUpload();
- Assert::IsTrue(bApiCalled);
+ vmUpload.mockRcClient.AssertNoPendingRequests();
- // published local leaderboard should be changed to promoted and have it's ID updated
- Assert::AreEqual(AssetCategory::Promoted, pLeaderboard.GetCategory());
+ // newly published local leaderboard should be changed to unpromoted and have it's ID updated
+ Assert::AreEqual(AssetCategory::Unpromoted, pLeaderboard.GetCategory());
Assert::AreEqual(7716U, pLeaderboard.GetID());
Assert::AreEqual(AssetChanges::None, pLeaderboard.GetChanges());
@@ -1118,6 +1103,7 @@ TEST_CLASS(AssetUploadViewModel_Tests)
TEST_METHOD(TestSingleLocalLeaderboardSubset)
{
AssetUploadViewModelHarness vmUpload;
+ vmUpload.mockUserContext.Initialize("User", "APITOKEN");
vmUpload.mockGameContext.Assets().AddAchievementSet(22, 33, L"Subset", ra::data::models::AchievementSetType::Bonus);
auto& pLeaderboard = vmUpload.AddLeaderboard(AssetCategory::Local, L"Title1", L"Desc1", "0xH1234=1",
"0xH1234=2", "0xH1234=3", "0xH2345", ra::data::Value::Format::Score);
@@ -1127,39 +1113,49 @@ TEST_CLASS(AssetUploadViewModel_Tests)
vmUpload.QueueAsset(pLeaderboard);
Assert::AreEqual({1U}, vmUpload.TaskCount());
- bool bApiCalled = false;
- vmUpload.mockServer.HandleRequest(
- [&bApiCalled](const ra::api::UpdateLeaderboard::Request& pRequest,
- ra::api::UpdateLeaderboard::Response& pResponse) {
- bApiCalled = true;
- Assert::AreEqual(33U, pRequest.GameId);
- Assert::AreEqual(std::wstring(L"Title1"), pRequest.Title);
- Assert::AreEqual(std::wstring(L"Desc1"), pRequest.Description);
- Assert::AreEqual(std::string("0xH1234=1"), pRequest.StartTrigger);
- Assert::AreEqual(std::string("0xH1234=2"), pRequest.SubmitTrigger);
- Assert::AreEqual(std::string("0xH1234=3"), pRequest.CancelTrigger);
- Assert::AreEqual(std::string("0xH2345"), pRequest.ValueDefinition);
- Assert::AreEqual(ra::data::Value::Format::Score, pRequest.Format);
- Assert::IsFalse(pRequest.LowerIsBetter);
- Assert::AreEqual(0U, pRequest.LeaderboardId);
-
- pResponse.LeaderboardId = 7716U;
- pResponse.Result = ra::api::ApiResult::Success;
- return true;
- });
+ vmUpload.mockRcClient.MockResponse(
+ "r=uploadleaderboard&u=User&t=APITOKEN&g=33&n=Title1&d=Desc1&s=0xH1234%3d1&b=0xH1234%3d2&c=0xH1234%3d3&l=0xH2345&w=0&f=SCORE&m=unpromoted&h=b9e48151adc764317296ac777ab550f1",
+ "{\"Success\":\"true\",\"LeaderboardID\":7716}"
+ );
vmUpload.DoUpload();
- Assert::IsTrue(bApiCalled);
+ vmUpload.mockRcClient.AssertNoPendingRequests();
- // published local leaderboard should be changed to promoted and have it's ID updated
- Assert::AreEqual(AssetCategory::Promoted, pLeaderboard.GetCategory());
+ // newly published local leaderboard should be changed to unpromoted and have it's ID updated
+ Assert::AreEqual(AssetCategory::Unpromoted, pLeaderboard.GetCategory());
Assert::AreEqual(7716U, pLeaderboard.GetID());
Assert::AreEqual(AssetChanges::None, pLeaderboard.GetChanges());
vmUpload.AssertSuccess(1);
}
+ TEST_METHOD(TestSingleLocalLeaderboardNoClaim)
+ {
+ AssetUploadViewModelHarness vmUpload;
+ vmUpload.mockUserContext.Initialize("User", "APITOKEN");
+ auto& pLeaderboard = vmUpload.AddLeaderboard(AssetCategory::Local, L"Title1", L"Desc1", "0xH1234=1", "0xH1234=2", "0xH1234=3", "0xH2345", ra::data::Value::Format::Score);
+ Assert::AreEqual(AssetChanges::Unpublished, pLeaderboard.GetChanges());
+
+ vmUpload.QueueAsset(pLeaderboard);
+ Assert::AreEqual({ 1U }, vmUpload.TaskCount());
+
+ vmUpload.mockRcClient.MockResponse(
+ "r=uploadleaderboard&u=User&t=APITOKEN&g=22&n=Title1&d=Desc1&s=0xH1234%3d1&b=0xH1234%3d2&c=0xH1234%3d3&l=0xH2345&w=0&f=SCORE&m=unpromoted&h=b9e48151adc764317296ac777ab550f1",
+ "{\"Success\":false,\"Status\":403,\"Code\":\"access_denied\",\"Error\":\"You must have an active claim on this game to perform this action.\"}"
+ );
+
+ vmUpload.DoUpload();
+
+ vmUpload.mockRcClient.AssertNoPendingRequests();
+
+ // newly published local leaderboard should be changed to unofficial and have it's ID updated
+ Assert::AreEqual(AssetCategory::Local, pLeaderboard.GetCategory());
+ Assert::AreEqual(AssetChanges::Unpublished, pLeaderboard.GetChanges());
+
+ vmUpload.AssertFailed(0, 1, L"* Title1: You must have an active claim on this game to perform this action.");
+ }
+
TEST_METHOD(TestRichPresence)
{
AssetUploadViewModelHarness vmUpload;
@@ -1442,9 +1438,6 @@ TEST_CLASS(AssetUploadViewModel_Tests)
AssetUploadViewModelHarness vmUpload;
vmUpload.mockUserContext.Initialize("User", "APITOKEN");
- const std::string sLastRequest = "r=submitcodenotes&u=User&t=APITOKEN&g=22&n=5296:Note+5296%0a5298:Note+5298%0a5300:Note+5300%0a5302:Note+5302%0a5304:Note+5304%0a5306:Note+5306%0a5308:Note+5308%0a5310:Note+5310%0a5312:Note+5312%0a5314:Note+5314%0a5316:Note+5316%0a5318:Note+5318%0a5320:Note+5320%0a5322:Note+5322%0a";
- vmUpload.mockRcClient.MockResponse(sLastRequest, "", 504); // dummy timeout response, but allows us to see if the request got called
-
constexpr size_t NumNotes = 614;
for (uint32_t nAddress = 0x1000; nAddress < 0x1000 + NumNotes * 2; nAddress += 2)
vmUpload.MemoryNotes().SetNote(nAddress, ra::util::String::Printf(L"Note %u", nAddress));
@@ -1461,7 +1454,6 @@ TEST_CLASS(AssetUploadViewModel_Tests)
Assert::AreEqual({ 100 }, vmUpload.GetProgress()); // 100%
vmUpload.mockRcClient.AssertNumRequestsHandled(7); // 6 * 100 + 1 * 14
- vmUpload.mockRcClient.AssertCalled(sLastRequest);
}
TEST_METHOD(TestMultipleMemoryNotesWithNewlines)