Changes
diff --git a/src/data_sqlite.cpp b/src/data_sqlite.cpp
index e065ec9..9740476 100644
--- a/src/data_sqlite.cpp
+++ b/src/data_sqlite.cpp
@@ -546,10 +546,14 @@ mw::E<std::optional<GameDefinitionSnapshot>> loadGameDefinition(
auto& [stored_short_name, display_name, description, visibility_value,
revision] = game_rows->front();
auto visibility = gameVisibilityFromInteger(visibility_value);
- if(!visibility || revision < 1)
+ if(!visibility)
+ {
+ return std::unexpected(std::move(visibility.error()));
+ }
+ if(revision < 1)
{
return std::unexpected(mw::runtimeError(
- "Database contains invalid game metadata"));
+ "Database contains an invalid game revision"));
}
if(scope == GameContentScope::PUBLIC_ONLY &&
*visibility == GameVisibility::INTERNAL)
@@ -2597,7 +2601,11 @@ mw::E<std::vector<Game>> DataSourceSQLite::getGames(
revision] : *rows)
{
auto visibility = gameVisibilityFromInteger(visibility_value);
- if(!visibility || revision < 1)
+ if(!visibility)
+ {
+ return std::unexpected(std::move(visibility.error()));
+ }
+ if(revision < 1)
{
return std::unexpected(mw::runtimeError(
"Database contains an invalid game revision"));
diff --git a/tests/app_integration_test.cpp b/tests/app_integration_test.cpp
index c0f80e3..42c8b66 100644
--- a/tests/app_integration_test.cpp
+++ b/tests/app_integration_test.cpp
@@ -377,7 +377,7 @@ TEST(AppIntegrationTest, ServesPagesAndStaticAssets)
{"short_name", "Invalid-Game"},
{"display_name", "Invalid Game"},
{"description", ""},
- {"visibility", "PUBLIC"},
+ {"visibility", "INTERNAL"},
};
auto invalid_game = client.Post(
"/collection/admin/games",
@@ -392,6 +392,25 @@ TEST(AppIntegrationTest, ServesPagesAndStaticAssets)
std::string::npos);
EXPECT_NE(invalid_game->body.find("Invalid-Game"),
std::string::npos);
+ const std::size_t retained_internal_choice = invalid_game->body.find(
+ "value=\"INTERNAL\"");
+ ASSERT_NE(retained_internal_choice, std::string::npos);
+ EXPECT_LT(
+ invalid_game->body.find("checked", retained_internal_choice),
+ invalid_game->body.find('>', retained_internal_choice));
+
+ const httplib::Params missing_visibility_fields = {
+ {"csrf_token", csrf_token},
+ {"short_name", "missingvisibility"},
+ {"display_name", "Missing visibility"},
+ {"description", ""},
+ };
+ auto missing_visibility = client.Post(
+ "/collection/admin/games",
+ session_headers,
+ missing_visibility_fields);
+ ASSERT_NE(missing_visibility, nullptr);
+ EXPECT_EQ(missing_visibility->status, 400);
const httplib::Params invalid_visibility_fields = {
{"csrf_token", csrf_token},
@@ -418,6 +437,34 @@ TEST(AppIntegrationTest, ServesPagesAndStaticAssets)
ASSERT_NE(duplicate_visibility, nullptr);
EXPECT_EQ(duplicate_visibility->status, 400);
+ const httplib::Params internal_create_fields = {
+ {"csrf_token", csrf_token},
+ {"short_name", "internalonly"},
+ {"display_name", "Internal only"},
+ {"description", ""},
+ {"visibility", "INTERNAL"},
+ };
+ auto internal_created = client.Post(
+ "/collection/admin/games",
+ session_headers,
+ internal_create_fields);
+ ASSERT_NE(internal_created, nullptr);
+ ASSERT_EQ(internal_created->status, 303) << internal_created->body;
+ auto internal_created_page = client.Get(
+ "/collection/admin/games/internalonly/edit", session_headers);
+ ASSERT_NE(internal_created_page, nullptr);
+ ASSERT_EQ(internal_created_page->status, 200);
+ EXPECT_NE(internal_created_page->body.find("Internal"),
+ std::string::npos);
+ const httplib::Params delete_internal_fields = {
+ {"csrf_token", csrf_token}, {"game_revision", "1"}};
+ auto internal_deleted = client.Post(
+ "/collection/admin/games/internalonly/delete",
+ session_headers,
+ delete_internal_fields);
+ ASSERT_NE(internal_deleted, nullptr);
+ ASSERT_EQ(internal_deleted->status, 303) << internal_deleted->body;
+
auto dynamic_game = client.Post(
"/collection/admin/games",
session_headers,
@@ -859,13 +906,6 @@ TEST(AppIntegrationTest, ServesPagesAndStaticAssets)
"/collection/admin/series/2", session_headers, edit_series_fields);
ASSERT_NE(edited_series, nullptr);
EXPECT_EQ(edited_series->status, 303) << edited_series->body;
- auto deleted_series = client.Post(
- "/collection/admin/series/2/delete",
- session_headers,
- csrf_fields);
- ASSERT_NE(deleted_series, nullptr);
- EXPECT_EQ(deleted_series->status, 303) << deleted_series->body;
-
auto player_authentication = client.Get("/collection/authentication");
ASSERT_NE(player_authentication, nullptr);
const std::string player_auth_cookie = responseCookie(
@@ -958,6 +998,48 @@ TEST(AppIntegrationTest, ServesPagesAndStaticAssets)
player_game_fields);
ASSERT_NE(player_game_create, nullptr);
EXPECT_EQ(player_game_create->status, 403);
+ auto promoted = client.Post(
+ "/collection/admin/users/2/promote",
+ session_headers,
+ csrf_fields);
+ ASSERT_NE(promoted, nullptr);
+ ASSERT_EQ(promoted->status, 303) << promoted->body;
+ const httplib::UploadFormDataItems creator_game_card_fields = {
+ {"csrf_token", player_csrf, "", ""},
+ {"game", "gh", "", ""},
+ {"game_revision", "1", "", ""},
+ {"name", "Creator Going Home", "", ""},
+ {"source_mode", "files", "", ""},
+ {"front", upload_bytes, "card.png", "image/png"},
+ };
+ auto creator_game_card = client.Post(
+ "/collection/cards", player_headers, creator_game_card_fields);
+ ASSERT_NE(creator_game_card, nullptr);
+ ASSERT_EQ(creator_game_card->status, 303) << creator_game_card->body;
+ auto creator_public_cards = client.Get(
+ "/collection/creator/cards", player_headers);
+ ASSERT_NE(creator_public_cards, nullptr);
+ ASSERT_EQ(creator_public_cards->status, 200);
+ EXPECT_NE(creator_public_cards->body.find("Creator Going Home"),
+ std::string::npos);
+ const httplib::Params player_pull_fields = {
+ {"csrf_token", player_csrf}};
+ auto initial_player_pull = client.Post(
+ "/collection/collection/pull",
+ player_headers,
+ player_pull_fields);
+ ASSERT_NE(initial_player_pull, nullptr);
+ ASSERT_EQ(initial_player_pull->status, 303)
+ << initial_player_pull->body;
+ EXPECT_EQ(
+ urlRequestPath(initial_player_pull->get_header_value("Location")),
+ created_path);
+ player_collection = client.Get(
+ "/collection/collection", player_headers);
+ ASSERT_NE(player_collection, nullptr);
+ ASSERT_EQ(player_collection->status, 200) << player_collection->body;
+ EXPECT_NE(player_collection->body.find("Edited upload"),
+ std::string::npos);
const httplib::Params internal_game_fields = {
{"csrf_token", csrf_token},
@@ -972,12 +1054,46 @@ TEST(AppIntegrationTest, ServesPagesAndStaticAssets)
internal_game_fields);
ASSERT_NE(internal_game, nullptr);
ASSERT_EQ(internal_game->status, 303) << internal_game->body;
+ auto administrator_games = client.Get(
+ "/collection/admin/games", session_headers);
+ ASSERT_NE(administrator_games, nullptr);
+ ASSERT_EQ(administrator_games->status, 200);
+ EXPECT_NE(administrator_games->body.find("Internal"),
+ std::string::npos);
+ auto internal_game_form = client.Get(
+ "/collection/admin/games/gh/edit", session_headers);
+ ASSERT_NE(internal_game_form, nullptr);
+ ASSERT_EQ(internal_game_form->status, 200);
+ const std::size_t internal_choice = internal_game_form->body.find(
+ "value=\"INTERNAL\"");
+ ASSERT_NE(internal_choice, std::string::npos);
+ EXPECT_NE(internal_game_form->body.find("checked", internal_choice),
+ std::string::npos);
auto concealed_card = client.Get(created_path, player_headers);
ASSERT_NE(concealed_card, nullptr);
EXPECT_EQ(concealed_card->status, 404);
EXPECT_EQ(
concealed_card->get_header_value("Cache-Control"),
"private, no-store");
+ player_collection = client.Get(
+ "/collection/collection", player_headers);
+ ASSERT_NE(player_collection, nullptr);
+ ASSERT_EQ(player_collection->status, 200) << player_collection->body;
+ EXPECT_EQ(player_collection->body.find("Edited upload"),
+ std::string::npos);
+ auto concealed_edit = client.Get(
+ created_path + "/edit", player_headers);
+ ASSERT_NE(concealed_edit, nullptr);
+ EXPECT_EQ(concealed_edit->status, 404);
+ EXPECT_EQ(concealed_edit->body, concealed_card->body);
+ EXPECT_EQ(
+ concealed_edit->get_header_value("Cache-Control"),
+ "private, no-store");
+ auto nonexistent_card = client.Get(
+ "/collection/cards/gh-999999", player_headers);
+ ASSERT_NE(nonexistent_card, nullptr);
+ EXPECT_EQ(nonexistent_card->status, 404);
+ EXPECT_EQ(nonexistent_card->body, concealed_card->body);
auto administrator_cards = client.Get(
"/collection/admin/cards", session_headers);
ASSERT_NE(administrator_cards, nullptr);
@@ -989,13 +1105,34 @@ TEST(AppIntegrationTest, ServesPagesAndStaticAssets)
EXPECT_EQ(administrator_card->status, 200);
EXPECT_NE(administrator_card->body.find(
"Not currently in the pull pool"), std::string::npos);
-
- auto promoted = client.Post(
- "/collection/admin/users/2/promote",
- session_headers,
- csrf_fields);
- ASSERT_NE(promoted, nullptr);
- ASSERT_EQ(promoted->status, 303) << promoted->body;
+ const httplib::UploadFormDataItems internal_edit_fields = {
+ {"csrf_token", csrf_token, "", ""},
+ {"name", "Internal edit", "", ""},
+ {"rarity", "4", "", ""},
+ {"revision", "2", "", ""},
+ {"game_revision", "2", "", ""},
+ {"source_mode", "files", "", ""},
+ {"front_action", "keep", "", ""},
+ {"foil_action", "keep", "", ""},
+ };
+ auto internal_edit = client.Post(
+ created_path + "/edit", session_headers, internal_edit_fields);
+ ASSERT_NE(internal_edit, nullptr);
+ ASSERT_EQ(internal_edit->status, 303) << internal_edit->body;
+ auto administrator_series = client.Get(
+ "/collection/admin/series", session_headers);
+ ASSERT_NE(administrator_series, nullptr);
+ ASSERT_EQ(administrator_series->status, 200);
+ EXPECT_NE(administrator_series->body.find("Edited series"),
+ std::string::npos);
+ EXPECT_NE(administrator_series->body.find("Internal"),
+ std::string::npos);
+ auto creator_internal_cards = client.Get(
+ "/collection/creator/cards", player_headers);
+ ASSERT_NE(creator_internal_cards, nullptr);
+ ASSERT_EQ(creator_internal_cards->status, 200);
+ EXPECT_EQ(creator_internal_cards->body.find("Creator Going Home"),
+ std::string::npos);
auto creator_game_create = client.Post(
"/collection/admin/games",
player_headers,
@@ -1007,6 +1144,13 @@ TEST(AppIntegrationTest, ServesPagesAndStaticAssets)
ASSERT_EQ(creator_new->status, 200) << creator_new->body;
EXPECT_EQ(creator_new->body.find("name=\"rarity\""), std::string::npos);
EXPECT_EQ(creator_new->body.find("Going Home"), std::string::npos);
+ EXPECT_EQ(creator_new->body.find("Edited series"), std::string::npos);
+ auto deleted_series = client.Post(
+ "/collection/admin/series/2/delete",
+ session_headers,
+ csrf_fields);
+ ASSERT_NE(deleted_series, nullptr);
+ EXPECT_EQ(deleted_series->status, 303) << deleted_series->body;
const httplib::UploadFormDataItems forged_internal_fields = {
{"csrf_token", player_csrf, "", ""},
{"game", "gh", "", ""},
@@ -1050,19 +1194,70 @@ TEST(AppIntegrationTest, ServesPagesAndStaticAssets)
creator_card_path + "/edit", session_headers, pool_edit_fields);
ASSERT_NE(pooled, nullptr);
ASSERT_EQ(pooled->status, 303) << pooled->body;
- const httplib::Params player_pull_fields = {
- {"csrf_token", player_csrf}};
- auto player_pull = client.Post(
- "/collection/collection/pull", player_headers, player_pull_fields);
- ASSERT_NE(player_pull, nullptr);
- ASSERT_EQ(player_pull->status, 303) << player_pull->body;
+ const httplib::Params administrator_pull_fields = {
+ {"csrf_token", csrf_token}};
+ auto administrator_pull = client.Post(
+ "/collection/collection/pull",
+ session_headers,
+ administrator_pull_fields);
+ ASSERT_NE(administrator_pull, nullptr);
+ ASSERT_EQ(administrator_pull->status, 303)
+ << administrator_pull->body;
EXPECT_EQ(
- urlRequestPath(player_pull->get_header_value("Location")),
+ urlRequestPath(administrator_pull->get_header_value("Location")),
creator_card_path);
- auto owned_card = client.Get(creator_card_path, player_headers);
+ auto owned_card = client.Get(creator_card_path, session_headers);
ASSERT_NE(owned_card, nullptr);
EXPECT_EQ(owned_card->status, 200) << owned_card->body;
EXPECT_NE(owned_card->body.find("100%"), std::string::npos);
+ const httplib::Params unprotected_visibility_fields = {
+ {"game_revision", "2"},
+ {"display_name", "Going Home"},
+ {"description", ""},
+ {"visibility", "PUBLIC"},
+ };
+ auto unprotected_visibility = client.Post(
+ "/collection/admin/games/gh",
+ session_headers,
+ unprotected_visibility_fields);
+ ASSERT_NE(unprotected_visibility, nullptr);
+ EXPECT_EQ(unprotected_visibility->status, 403);
+ const httplib::Params revisionless_visibility_fields = {
+ {"csrf_token", csrf_token},
+ {"display_name", "Going Home"},
+ {"description", ""},
+ {"visibility", "PUBLIC"},
+ };
+ auto revisionless_visibility = client.Post(
+ "/collection/admin/games/gh",
+ session_headers,
+ revisionless_visibility_fields);
+ ASSERT_NE(revisionless_visibility, nullptr);
+ EXPECT_EQ(revisionless_visibility->status, 400);
+ const httplib::Params public_game_fields = {
+ {"csrf_token", csrf_token},
+ {"game_revision", "2"},
+ {"display_name", "Going Home"},
+ {"description", ""},
+ {"visibility", "PUBLIC"},
+ };
+ auto public_game = client.Post(
+ "/collection/admin/games/gh",
+ session_headers,
+ public_game_fields);
+ ASSERT_NE(public_game, nullptr);
+ ASSERT_EQ(public_game->status, 303) << public_game->body;
+ auto revealed_card = client.Get(created_path, player_headers);
+ ASSERT_NE(revealed_card, nullptr);
+ ASSERT_EQ(revealed_card->status, 200) << revealed_card->body;
+ EXPECT_NE(revealed_card->body.find("Internal edit"),
+ std::string::npos);
+ player_collection = client.Get(
+ "/collection/collection", player_headers);
+ ASSERT_NE(player_collection, nullptr);
+ ASSERT_EQ(player_collection->status, 200) << player_collection->body;
+ EXPECT_NE(player_collection->body.find("Internal edit"),
+ std::string::npos);
auto deleted = client.Post(
created_path + "/delete",
session_headers,
diff --git a/tests/card_service_test.cpp b/tests/card_service_test.cpp
index 0150822..8aa6796 100644
--- a/tests/card_service_test.cpp
+++ b/tests/card_service_test.cpp
@@ -928,6 +928,52 @@ TEST(CardServiceTest, RejectsCreatorMutationsForInternalGames)
EXPECT_EQ(create_error->code, 422);
EXPECT_EQ(create_error->msg, "Unknown game");
+ const std::filesystem::path administrator_staging =
+ temporary.path() / ".staging/administrator";
+ std::filesystem::create_directories(administrator_staging);
+ const std::filesystem::path administrator_front =
+ administrator_staging / "upload_front";
+ writePng(administrator_front);
+ auto administrator_created = service.createGameCard(
+ 1,
+ {"Administrator card", std::nullopt, std::nullopt, 0,
+ administrator_staging, administrator_front, std::nullopt,
+ std::nullopt, false},
+ "internal",
+ 1,
+ {},
+ {});
+ ASSERT_TRUE(administrator_created) << administrator_created.error().msg();
+ auto administrator_card = (*data_source)->getCard(
+ {"internal", 1}, GameContentScope::INCLUDE_INTERNAL);
+ ASSERT_TRUE(administrator_card);
+ ASSERT_TRUE(*administrator_card);
+ const std::filesystem::path administrator_edit_staging =
+ temporary.path() / ".staging/administrator-edit";
+ std::filesystem::create_directories(administrator_edit_staging);
+ auto administrator_updated = service.updateGameCard(
+ 1,
+ {**administrator_card, 1, "Administrator edit", std::nullopt,
+ std::nullopt, 0, administrator_edit_staging,
+ FrontAssetAction::KEEP, FoilAssetAction::KEEP, std::nullopt,
+ std::nullopt, std::nullopt, false},
+ 1,
+ {},
+ {});
+ ASSERT_TRUE(administrator_updated) << administrator_updated.error().msg();
+ administrator_card = (*data_source)->getCard(
+ {"internal", 1}, GameContentScope::INCLUDE_INTERNAL);
+ ASSERT_TRUE(administrator_card);
+ ASSERT_TRUE(*administrator_card);
+ EXPECT_EQ((**administrator_card).name, "Administrator edit");
+ auto administrator_deleted = service.deleteCard(
+ 1, **administrator_card);
+ ASSERT_TRUE(administrator_deleted) << administrator_deleted.error().msg();
+ administrator_card = (*data_source)->getCard(
+ {"internal", 1}, GameContentScope::INCLUDE_INTERNAL);
+ ASSERT_TRUE(administrator_card);
+ EXPECT_FALSE(*administrator_card);
+
const std::filesystem::path public_staging =
temporary.path() / ".staging/public";
std::filesystem::create_directories(public_staging);
diff --git a/tests/data_sqlite_test.cpp b/tests/data_sqlite_test.cpp
index 6a4c6af..9bf0a64 100644
--- a/tests/data_sqlite_test.cpp
+++ b/tests/data_sqlite_test.cpp
@@ -382,17 +382,30 @@ TEST(DataSourceSQLiteTest, ScopesInternalGameContentAndPulls)
*internal_id, GameContentScope::PUBLIC_ONLY);
ASSERT_TRUE(hidden_fields);
EXPECT_FALSE(*hidden_fields);
+ auto creator_cards = (*data_source)->getCardsByCreator(
+ 1, GameContentScope::PUBLIC_ONLY);
+ ASSERT_TRUE(creator_cards);
+ ASSERT_EQ(creator_cards->size(), 1);
+ EXPECT_EQ(creator_cards->front().name, "Public card");
auto public_series_list = (*data_source)->getSeries(
GameContentScope::PUBLIC_ONLY);
+ auto hidden_series = (*data_source)->getSeries(
+ *internal_series, GameContentScope::PUBLIC_ONLY);
auto public_collection = (*data_source)->getCollection(
1, GameContentScope::PUBLIC_ONLY);
+ auto administrator_collection = (*data_source)->getCollection(
+ 1, GameContentScope::INCLUDE_INTERNAL);
auto pool = (*data_source)->getPoolCards();
ASSERT_TRUE(public_series_list);
+ ASSERT_TRUE(hidden_series);
ASSERT_TRUE(public_collection);
+ ASSERT_TRUE(administrator_collection);
ASSERT_TRUE(pool);
EXPECT_EQ(public_series_list->size(), 1);
+ EXPECT_FALSE(*hidden_series);
EXPECT_EQ(public_collection->size(), 1);
+ EXPECT_EQ(administrator_collection->size(), 2);
EXPECT_EQ(pool->size(), 1);
transaction = (*data_source)->beginTransaction();
@@ -407,13 +420,17 @@ TEST(DataSourceSQLiteTest, ScopesInternalGameContentAndPulls)
ASSERT_TRUE((*transaction)->commit());
public_cards = (*data_source)->getCards(GameContentScope::PUBLIC_ONLY);
+ public_series_list = (*data_source)->getSeries(
+ GameContentScope::PUBLIC_ONLY);
public_collection = (*data_source)->getCollection(
1, GameContentScope::PUBLIC_ONLY);
pool = (*data_source)->getPoolCards();
ASSERT_TRUE(public_cards);
+ ASSERT_TRUE(public_series_list);
ASSERT_TRUE(public_collection);
ASSERT_TRUE(pool);
EXPECT_EQ(public_cards->size(), 2);
+ EXPECT_EQ(public_series_list->size(), 2);
EXPECT_EQ(public_collection->size(), 2);
EXPECT_EQ(pool->size(), 2);
}
@@ -796,6 +813,67 @@ TEST(DataSourceSQLiteTest, RejectsInvalidGameVisibility)
static_cast<GameVisibility>(2), 1}));
}
+TEST(DataSourceSQLiteTest, DefaultsGameVisibilityToPublicInteger)
+{
+ TemporaryDatabase database;
+ auto data_source = prepareDataSource(database.path());
+ ASSERT_TRUE(data_source) << data_source.error().msg();
+ data_source->reset();
+
+ auto connection = mw::SQLite::connectFile(database.path().string());
+ ASSERT_TRUE(connection);
+ ASSERT_TRUE((*connection)->execute(
+ "INSERT INTO games(short_name, display_name) "
+ "VALUES ('default', 'Default');"));
+ auto stored = (*connection)->evalToValue<std::int64_t>(
+ "SELECT visibility FROM games WHERE short_name = 'default';");
+ auto stored_type = (*connection)->evalToValue<std::string>(
+ "SELECT typeof(visibility) FROM games "
+ "WHERE short_name = 'default';");
+ ASSERT_TRUE(stored);
+ ASSERT_TRUE(stored_type);
+ EXPECT_EQ(*stored, 0);
+ EXPECT_EQ(*stored_type, "integer");
+ connection->reset();
+
+ auto reopened = DataSourceSQLite::fromFile(database.path());
+ ASSERT_TRUE(reopened);
+ auto games = (*reopened)->getGames(
+ GameContentScope::INCLUDE_INTERNAL);
+ ASSERT_TRUE(games);
+ ASSERT_EQ(games->size(), 1);
+ EXPECT_EQ(games->front().visibility, GameVisibility::PUBLIC);
+}
+
+TEST(DataSourceSQLiteTest, RejectsCorruptGameVisibilityOnRead)
+{
+ TemporaryDatabase database;
+ auto data_source = prepareDataSource(database.path());
+ ASSERT_TRUE(data_source) << data_source.error().msg();
+ auto transaction = (*data_source)->beginTransaction();
+ ASSERT_TRUE(transaction);
+ ASSERT_TRUE((*transaction)->insertGame(
+ {"corrupt", "Corrupt", "", GameVisibility::PUBLIC, 1}));
+ ASSERT_TRUE((*transaction)->commit());
+ data_source->reset();
+
+ auto connection = mw::SQLite::connectFile(database.path().string());
+ ASSERT_TRUE(connection);
+ ASSERT_TRUE((*connection)->execute(
+ "PRAGMA ignore_check_constraints = ON;"));
+ ASSERT_TRUE((*connection)->execute(
+ "UPDATE games SET visibility = 2 WHERE short_name = 'corrupt';"));
+ connection->reset();
+
+ auto reopened = DataSourceSQLite::fromFile(database.path());
+ ASSERT_TRUE(reopened);
+ auto games = (*reopened)->getGames(
+ GameContentScope::INCLUDE_INTERNAL);
+ ASSERT_FALSE(games);
+ EXPECT_NE(games.error().msg().find("invalid game visibility"),
+ std::string::npos) << games.error().msg();
+}
+
/// Verify fresh initialization and reopening do not create any games.
TEST(DataSourceSQLiteTest, StartsAndReopensWithoutGames)
{
diff --git a/tests/game_service_test.cpp b/tests/game_service_test.cpp
index 8ae7618..8d6d9e9 100644
--- a/tests/game_service_test.cpp
+++ b/tests/game_service_test.cpp
@@ -119,6 +119,19 @@ TEST(GameServiceTest, MutatesDefinitionsAndRejectsStaleRevisions)
GameVisibility::PUBLIC);
ASSERT_TRUE(created) << created.error().msg();
EXPECT_EQ(*created, "demo");
+ auto internal_created = service.createGame(
+ fixture->administrator_id,
+ "internal",
+ "Internal Game",
+ "",
+ GameVisibility::INTERNAL);
+ ASSERT_TRUE(internal_created) << internal_created.error().msg();
+ auto internal_definition = fixture->data_source->getGameDefinition(
+ "internal", GameContentScope::INCLUDE_INTERNAL);
+ ASSERT_TRUE(internal_definition);
+ ASSERT_TRUE(*internal_definition);
+ EXPECT_EQ((**internal_definition).game.visibility,
+ GameVisibility::INTERNAL);
auto integer_id = service.createField(
fixture->administrator_id,
@@ -186,6 +199,16 @@ TEST(GameServiceTest, MutatesDefinitionsAndRejectsStaleRevisions)
"Renamed Game",
"New description",
GameVisibility::INTERNAL));
+ auto stale_visibility = service.updateGame(
+ fixture->administrator_id,
+ "demo",
+ 6,
+ "Renamed Game",
+ "New description",
+ GameVisibility::PUBLIC);
+ ASSERT_FALSE(stale_visibility);
+ ASSERT_NE(httpError(stale_visibility.error()), nullptr);
+ EXPECT_EQ(httpError(stale_visibility.error())->code, 409);
auto definition = fixture->data_source->getGameDefinition(
"demo", GameContentScope::INCLUDE_INTERNAL);