{"record":{"id":"9653d96a92168e1d","repo":"ppy/osu","slug":"accessing-the-client-side-api-room-via-nameof-tes","errorCode":null,"errorMessage":"Accessing the client-side API room via {nameof(TestMultiplayerClient)} is unsafe. Use {nameof(ClientAPIRoom)} if this was intended.","messagePattern":"Accessing the client-side API room via (.+?) is unsafe\\. Use (.+?) if this was intended\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs","lineNumber":63,"sourceCode":"        public Room? ClientAPIRoom => base.APIRoom;\r\n\r\n        /// <summary>\r\n        /// The local client's <see cref=\"MultiplayerRoom\"/>. This is not always equivalent to the server-side room.\r\n        /// </summary>\r\n        public MultiplayerRoom? ClientRoom => base.Room;\r\n\r\n        /// <summary>\r\n        /// The server's <see cref=\"Room\"/>. This is always up-to-date.\r\n        /// </summary>\r\n        public Room? ServerAPIRoom { get; private set; }\r\n\r\n        /// <summary>\r\n        /// The server's <see cref=\"MultiplayerRoom\"/>. This is always up-to-date.\r\n        /// </summary>\r\n        public MultiplayerRoom? ServerRoom { get; private set; }\r\n\r\n        [Obsolete]\r\n        protected new Room APIRoom => throw new InvalidOperationException($\"Accessing the client-side API room via {nameof(TestMultiplayerClient)} is unsafe. \"\r\n                                                                          + $\"Use {nameof(ClientAPIRoom)} if this was intended.\");\r\n\r\n        [Obsolete]\r\n        public new MultiplayerRoom Room => throw new InvalidOperationException($\"Accessing the client-side room via {nameof(TestMultiplayerClient)} is unsafe. \"\r\n                                                                               + $\"Use {nameof(ClientRoom)} if this was intended.\");\r\n\r\n        public new MultiplayerRoomUser? LocalUser => ServerRoom?.Users.SingleOrDefault(u => u.User?.Id == API.LocalUser.Value.Id);\r\n\r\n        public Action<MultiplayerRoom>? RoomSetupAction;\r\n\r\n        public bool RoomJoined { get; private set; }\r\n\r\n        [Resolved]\r\n        private IAPIProvider api { get; set; } = null!;\r\n\r\n        private MultiplayerPlaylistItem? currentItem => ServerRoom?.Playlist[currentIndex];\r\n        private int currentIndex;\r\n        private long lastPlaylistItemId;\r","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs#L45-L81","documentation":"TestMultiplayerClient is a test double that holds BOTH a server-side Room (ServerAPIRoom) and the inherited client-side Room (exposed as ClientAPIRoom). The inherited base MultiplayerClient.APIRoom would return the client view, which is ambiguous and often not what a test asserts against, so it is shadowed with `new` and marked [Obsolete] to throw. The throw forces the author to disambiguate by naming the room explicitly.","triggerScenarios":"Reading `client.APIRoom` (the protected inherited property) in a multiplayer test scene or helper, instead of ClientAPIRoom (client view) or ServerAPIRoom (server/authoritative view).","commonSituations":"Code copy-pasted from production MultiplayerClient consumers into a test; an IDE auto-completing to the shadowed base property; refactors that previously used base.APIRoom.","solutions":["Use ClientAPIRoom when you want what the local client received.","Use ServerAPIRoom when you want the authoritative server state the test double maintains.","If you truly need the inherited behavior, note that ClientAPIRoom already returns base.APIRoom, so prefer it."],"exampleFix":"// before\nRoom? room = multiplayerClient.APIRoom;\n\n// after\nRoom? room = multiplayerClient.ServerAPIRoom; // authoritative server view\n// or: multiplayerClient.ClientAPIRoom; // client view","handlingStrategy":"validation","validationCode":"// never read the shadowed base APIRoom; pick the explicit view up front\nRoom? room = wantServerView ? multiplayerClient.ServerAPIRoom : multiplayerClient.ClientAPIRoom;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat the [Obsolete] throw as a compile/lint nudge, not a runtime path to catch.","Search tests for `.APIRoom` and redirect each to ClientAPIRoom or ServerAPIRoom.","When porting production MultiplayerClient code into a test, audit every base-property access."],"tags":["test-infra","multiplayer","obsolete","property-shadowing"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}