feat: Add regression tests for room cloning functionality; ensure cloned rooms return full details

This commit is contained in:
Alexa
2026-07-13 00:54:12 +03:00
parent 2c749c2677
commit 5460cc3601
3 changed files with 101 additions and 9 deletions
@@ -1046,8 +1046,14 @@ public class RoomsModerationController(DorkNetDbContext db) : ControllerBase
.Where(s => s.RoomId == source.Id)
.OrderBy(s => s.OrderIndex)
.ToListAsync();
// Scene ids need to be explicit for the same reason room ids do —
// RoomSceneEntity.Id is an auto/identity column whose Postgres sequence
// lags behind the manually-seeded rows, so relying on DB generation
// collides and the whole clone SaveChanges 500s.
var nextSceneId = await db.RoomScenes.MaxAsync(s => (long?)s.Id) ?? 0L;
var clonedScenes = sourceScenes.Select(s => new RoomSceneEntity
{
Id = ++nextSceneId,
RoomId = clone.Id,
OrderIndex = s.OrderIndex,
Name = s.Name,
@@ -1069,10 +1075,14 @@ public class RoomsModerationController(DorkNetDbContext db) : ControllerBase
// The 2023 client's clone (RecNet.Runtime NLDBPDCNNCF.GDHIIAHCBMN)
// deserializes the response as the FULL room-details object
// (FGCPNAACHIK — the same type get-by-id / rename return), NOT the
// slim list-room shape. Returning ToWireRoom here made the strict
// reader fail → "Failed to copy room: Failed to clone room".
return Ok(RoomsController.BuildRoomDetails(clone, clonedScenes));
// (FGCPNAACHIK — the same type get-by-id / rename return on the
// roomserver host), NOT the slim list-room shape. This is the
// roomserver-family path (rooms/{id}/clone), so use
// BuildRoomServerDetails — the exact shape GET rooms/{id} returns for
// the 2023 client. Returning ToWireRoom (or the wrong details variant)
// made the strict reader fail → "Failed to copy room: Failed to clone
// room".
return Ok(RoomsController.BuildRoomServerDetails(clone, clonedScenes));
}
public sealed class CloneBareRequest { public string? Name { get; set; } }
@@ -564,11 +564,23 @@ public class CdnController(
r.IsAGRoom && r.CurrentDataBlobName == fileName))
return true;
if (fileName.StartsWith("room_", StringComparison.OrdinalIgnoreCase) &&
fileName.EndsWith("_v1.dat", StringComparison.OrdinalIgnoreCase) &&
long.TryParse(fileName[5..^7], out var roomId) &&
await db.Rooms.AsNoTracking().AnyAsync(r => r.Id == roomId && r.IsAGRoom))
return true;
// Synthetic/default room-data blob names for rooms with no saved blob:
// the CURRENT form is room_{id}_dorknet_v8.dat
// (RoomService.SyntheticDefaultRoomDataBlobName) and the legacy form is
// room_{id}_v1.dat. Baked RRO rooms (empty CurrentDataBlobName) load
// their permissions from this synthetic blob, so it MUST overlay too —
// otherwise MakerPen only appears in RRO rooms that happen to have a
// real saved blob (which is why it worked for Rec Center / Stunt Runner
// but not the rest). Parse the id from any room_{id}_* form.
if (fileName.StartsWith("room_", StringComparison.OrdinalIgnoreCase))
{
var rest = fileName["room_".Length..];
var underscore = rest.IndexOf('_');
if (underscore > 0 &&
long.TryParse(rest[..underscore], out var roomId) &&
await db.Rooms.AsNoTracking().AnyAsync(r => r.Id == roomId && r.IsAGRoom))
return true;
}
return await (
from scene in db.RoomScenes.AsNoTracking()