DeckHashTest.RepeatTest exceeds the per-test 60 s timeout on the Arch
image while finishing in under a second everywhere else, so the Arch leg
is red on every pull request. The suite is labelled `bench` and documented
as kept out of the default run; ctest now skips that label outside the
coverage leg, which still wants those lines exercised.
Add --set key=value to override a section-qualified scalar configuration key for a single run and --print-config to print the effective configuration, with secrets redacted. SettingsCache owns the override map, so reads see the override without rewriting the ini file, and rebuilds the disallowed-name regexps when users/disallowedregexp is overridden. Bare keys and array-scoped keys are rejected because array loops read them relative to the active group, where a full-key override would either be ignored or rewrite every entry.
Cover the ini, default, override, override-only-key, regexp-rebuild, secret-redaction, and parse paths with a server-labelled gtest.
* [Server] Smoke-test the dev server end to end
Spawns servatrice-dev on an ephemeral port and drives a real RemoteClient through login, room list, room join and game creation, asserting each response and the Event_GameJoined payload. The test is WITH_SERVER-gated, links only the client remote library, and keeps the dev server's throwaway settings inside a temporary directory.
* [Server] Publish the dev server's port through a file
The smoke test parsed the listening port out of the dev server's stdout,
but the server announces it from the main thread while the logger thread
writes to the same stream. A log line can land inside the announcement —
Arch CI caught it — leaving the digits on the next line, so the test read
nothing and failed.
Add --port-file, written through QSaveFile once the server listens, and
read the port from there instead of from the log. The file is either
absent or complete, so logging cannot corrupt the handshake.
* [Tests] Tick the event loop while waiting for the port file
The port-file wait loop slept with QThread::msleep and never reached the
event dispatcher. QProcess fills its read buffers and flips its state only
from its socket notifiers, so drainProcessOutput() was a no-op and a child
that died before publishing its port went unnoticed until the full 15 s
startup timeout, with empty stdout and stderr in the failure message.
Pump the event loop instead, as the previous waitForReadyRead loop did.
Servatrice_ConnectionPool stored and returned a concrete
Servatrice_DatabaseInterface pointer, so the pool that decides which database a
socket handler runs against could not hold any other Server_DatabaseInterface.
Retype the constructor, member and accessor, and include the interface header
instead of the Servatrice one. The shipped path still builds MySQL pools and
converts implicitly; behaviour is unchanged.
AbstractServerSocketInterface and its Tcp and Websocket subclasses still took a
concrete Servatrice_DatabaseInterface pointer, so the handler could not be
constructed with anything but the MySQL implementation even though its body now
speaks only Server_DatabaseInterface. Retype the three constructors and drop the
now-unused servatrice_database_interface.h include. Callers pass the pool's
Servatrice_DatabaseInterface, which converts to the base pointer, so behaviour
is unchanged.
deckListHelper still prepared the folder and deck-file statements inline through
Servatrice_DatabaseInterface, and the four deck commands reached checkSql,
getUserIdInDB and getDeckFromDatabase through a concrete pointer. Add
DeckListFolderRecord, DeckListFileRecord, getDeckListFolders and
getDeckListFiles to Server_DatabaseInterface with fail-closed defaults, serve
all four commands from the injected interface, and drop the sqlInterface member
so the handler no longer holds a database implementation at all. Folder
ordering, recursion order, public-only pruning and every response code are
unchanged.
The four card-art commands still prepared their statements inline through
Servatrice_DatabaseInterface: the two card_art_params updates in
cmdSetCardArtParams, the rule upsert and delete, and the rule listing that
mapped rows to protocol entries. Add CardArtRuleRecord, upsertCardArtRule,
removeCardArtRule, getCardArtRules, setCardArtParams and clearCardArtParams to
Server_DatabaseInterface with fail-closed defaults, implement them with the
previous statements, and keep the validation, clamping, in-memory userInfo sync
and broadcast in the handler.
The moderator account commands still reached Servatrice_DatabaseInterface through
its concrete class: cmdResetUserPassword ran the privilege lookup inline and
called the concrete-only changeUserPasswordWithLegacyBackup, while the avatar,
banner and blacklisted-banner commands called concrete-only helpers. Promote
those four methods to Server_DatabaseInterface, move RemoveUserBannerResult into
the interface, and add getUserAdminFlags for the reset privilege lookup. The
escalation guard, audit records, had_banner response field and every response
code stay in the handler.
cmdAccountEdit assembled a dynamic UPDATE for the real name, email and country
columns in the socket handler, and cmdAccountImage wrote the avatar blob there
too. Add AccountUpdateRecord, updateUserAccount and setUserAvatar to
Server_DatabaseInterface with fail-closed defaults, implement them with the
previous query text and bind values, and keep the password gate, the email
permission check, the empty-edit early return and the in-memory userInfo updates
in the handler.
cmdBanFromServer, the admin-flag helpers, cmdForceActivateUser and the
admin-notes commands still prepared their statements inline through
Servatrice_DatabaseInterface, and addWarning was only reachable through the
concrete Servatrice class. Add setAdminFlag, getUserToken, getAdminNotes,
setAdminNotes, addBan and getUserNamesByClientId to Server_DatabaseInterface
with fail-closed defaults, promote addWarning, and model the outcomes as
UserFieldResult and AdminFlagUpdateResult. Notifications, the audit record,
the ban disconnect fan-out and every response code stay in the handler.
cmdReportAssign, cmdReportResolve, cmdReportReopen and cmdReportAddComment still
ran their lookups and updates inline through Servatrice_DatabaseInterface. Add
assignReport, resolveReport, reopenReport, getUserNameById,
getReportNotificationTarget, countReportCommentsInLastHour,
getReportCommentTarget and addReportComment to Server_DatabaseInterface with
fail-closed defaults, and model the outcomes as ReportAssignResult and
ReportUpdateResult so "no such report", "wrong state" and "query failed" stay
distinguishable. Authorization checks, audit records, notification recipients
and every response code stay in the handler.
cmdReportStats and cmdReportUserInfo still ran their queries inline through
Servatrice_DatabaseInterface and built the response in the socket handler. Add
ReportStatsRecord, ReportUserInfoRecord and their count records to
Server_DatabaseInterface with fail-closed defaults, implement getReportStats and
getReportUserInfo with the previous statements and query order, and keep
presence flags for the fields the old code set only on a successful query so
the proto2 wire output is unchanged. The stats cache decision and every response
code stay in the handler.
cmdReportList, cmdReportMyList and cmdReportDetails still prepared their SELECTs
inline through Servatrice_DatabaseInterface and mapped the result columns to
protocol records in the socket handler. Add getReportList,
getReportsSubmittedBy, getReportForDetails and getReportComments to
Server_DatabaseInterface with fail-closed defaults and a ReportLookupResult
outcome, implement them with the previous statements, joins and null handling,
and keep the detail authorization check and the tolerated comment-query failure
in the handler in the same order. Response codes are unchanged.
cmdReport and sendPendingReportNotifications still issued their SQL inline
through Servatrice_DatabaseInterface: the daily report cap, the reported-user
lookup, the report insert, both pending-notification selects and the notified
flips. Add the two notification records and seven report methods to
Server_DatabaseInterface with fail-closed defaults, implement them with the
previous statements and binds, and let both functions ask the injected
interface. Check order, response codes and notification payloads are unchanged.
cmdForgotPasswordRequest, continuePasswordRequest, cmdForgotPasswordReset,
cmdForgotPasswordChallenge, cmdRequestPasswordSalt and cmdSubmitPasswordVerifier
still called Servatrice_DatabaseInterface, and two of them validated the account
token and email through the generic validateTableColumnStringData helper. Add
doesForgotPasswordExist, addForgotPassword and getUsableLegacyBackup to
Server_DatabaseInterface with fail-closed defaults, replace the table/column
probes with userTokenMatches and userEmailMatches using the same statement and
case-insensitive comparison, and let the commands ask the injected interface.
Audit records, check order and every response code are unchanged.
cmdRegisterAccount and cmdActivateAccount still called
Servatrice_DatabaseInterface for username validation, user existence, the
per-email account limit, ban checks, registration and activation, and
cmdRegisterAccount issued the activation-email insert inline. Add
addActivationEmail to Server_DatabaseInterface with a false default, implement
it with the same statement and bind value, and let both commands ask the
injected interface. Validation order and every response code are unchanged.
cmdGetServerStats reached through Servatrice_DatabaseInterface for checkSql and
getLatestUptimeSnapshot, a concrete-only method. Move the UptimeSnapshot record
to Server_DatabaseInterface with an invalid default for non-SQL stores, mark the
Servatrice implementation as an override, and let the command ask the injected
interface. The statement, its bind value and the invalid-snapshot response are
unchanged.
createHashForReplay, cmdReplayGetCode, cmdReplaySubmitCode,
cmdReplayDownloadByGameId and cmdGrantReplayAccess still issued their own SQL
through Servatrice_DatabaseInterface. Add checkReplayAccess, getReplayName,
getReplayHashSource, addReplayAccess, getLatestReplayForGame and
grantReplayAccessByName to Server_DatabaseInterface, implement them with the
same statements, bind order and response mapping, and let the commands ask the
injected interface. Hashing and code parsing stay in the handler; the shared
insert is reused by both grant paths.
cmdReplayList, cmdReplayDownload, cmdReplayModifyMatch and cmdReplayDeleteMatch issued their own SQL through Servatrice_DatabaseInterface. Add ReplayMatchRecord/ReplayEntry and the ReplayAccessResult/ReplayWriteResult enums to Server_DatabaseInterface, implement the list, data, do-not-hide and delete operations with the same statements and response mapping, and let the commands ask the injected interface. checkSql stays in the two write commands where it already was.
The five deck-share commands reached through Servatrice_DatabaseInterface for share lookups, and cmdDeckShareCreate still issued the per-user rate-limit count and the folder deck listing inline. Move DeckShareItemRecord and DeckShareSummaryRecord to Server_DatabaseInterface, declare the share operations plus getDeckSharesCreatedToday and getDeckFilesInFolder there with failing defaults, and switch the commands to the injected interface. The MySQL statements and every response code stay as they were, including the -1-on-failure sentinel for the count.
addAuditRecord existed only on Servatrice_DatabaseInterface, so 37 moderator and admin commands reached through the concrete class just to append a log row. Declare it on Server_DatabaseInterface with an empty default, mark the Servatrice implementation as an override, and switch the handler's call sites to the injected interface. No SQL moves; the argument hashes are unchanged.
Deleting a deck, inserting an uploaded deck, and updating one in place were SQL statements in the socket handler, including the dynamic is_public clause and the follow-up visibility read. Add DeckFileData and DeckFileWriteResult to Server_DatabaseInterface with create/update/delete operations, implement them in Servatrice_DatabaseInterface with the same statements and bind order, and let cmdDeckUpload and cmdDeckDel ask the injected interface.
Setting a deck or folder public, creating a folder, and deleting a folder tree were SQL statements in the socket handler. Declare them on Server_DatabaseInterface with a DeckStorageResult so the caller can still tell a missing row from a failed write, implement them in Servatrice_DatabaseInterface with the same statements, and let the three folder commands ask the injected interface. Readiness probing keeps its previous ignore-the-result shape.
Resolving a deck folder path, reading a deck's owner, and walking its folder chain for public visibility were SQL queries living in the socket handler, keyed to the logged-in user through userInfo. Declare the three lookups on Server_DatabaseInterface, implement them in Servatrice_DatabaseInterface with the user id as an explicit parameter, and let the deck commands ask the injected interface.