|
5 | 5 | "encoding/base64" |
6 | 6 | "fmt" |
7 | 7 | "net/http" |
| 8 | + "net/url" |
8 | 9 | "os" |
9 | 10 | "strings" |
10 | 11 | "testing" |
@@ -113,6 +114,106 @@ func TestContractSaveLatestMissingAndSuccessShape(t *testing.T) { |
113 | 114 | } |
114 | 115 | } |
115 | 116 |
|
| 117 | +func TestContractSaveLatestFallsBackToCanonicalTrackContext(t *testing.T) { |
| 118 | + h := newContractHarness(t) |
| 119 | + |
| 120 | + upload := uploadSave(t, h, "/saves", map[string]string{ |
| 121 | + "rom_sha1": "pokemon-cloud-rom", |
| 122 | + "slotName": "default", |
| 123 | + "system": "gameboy", |
| 124 | + }, "Pokemon Noise Loop.srm", buildNonBlankPayload(8192, 0x23)) |
| 125 | + saveObj := mustObject(t, upload["save"], "save") |
| 126 | + wantID := mustString(t, saveObj["id"], "save.id") |
| 127 | + wantSHA := mustString(t, saveObj["sha256"], "save.sha256") |
| 128 | + |
| 129 | + withoutContext := h.request(http.MethodGet, "/save/latest?romSha1=pokemon-local-rom&slotName=default", nil) |
| 130 | + assertStatus(t, withoutContext, http.StatusOK) |
| 131 | + withoutBody := decodeJSONMap(t, withoutContext.Body) |
| 132 | + if mustBool(t, withoutBody["exists"], "exists") { |
| 133 | + t.Fatalf("expected exact latest miss without track context: %s", prettyJSON(withoutBody)) |
| 134 | + } |
| 135 | + |
| 136 | + query := url.Values{} |
| 137 | + query.Set("romSha1", "pokemon-local-rom") |
| 138 | + query.Set("slotName", "default") |
| 139 | + query.Set("filename", "Pokemon Noise Loop.srm") |
| 140 | + query.Set("system", "gameboy") |
| 141 | + withContext := h.request(http.MethodGet, "/save/latest?"+query.Encode(), nil) |
| 142 | + assertStatus(t, withContext, http.StatusOK) |
| 143 | + withBody := decodeJSONMap(t, withContext.Body) |
| 144 | + if !mustBool(t, withBody["exists"], "exists") { |
| 145 | + t.Fatalf("expected latest fallback hit with track context: %s", prettyJSON(withBody)) |
| 146 | + } |
| 147 | + if got := mustString(t, withBody["id"], "id"); got != wantID { |
| 148 | + t.Fatalf("expected fallback id %q, got %q", wantID, got) |
| 149 | + } |
| 150 | + if got := mustString(t, withBody["sha256"], "sha256"); got != wantSHA { |
| 151 | + t.Fatalf("expected fallback sha %q, got %q", wantSHA, got) |
| 152 | + } |
| 153 | + if indexed, ok := h.app.findSaveRecordByID(wantID); !ok || indexed.Summary.SHA256 != wantSHA { |
| 154 | + t.Fatalf("expected indexed lookup to match uploaded save, got ok=%v record=%+v", ok, indexed.Summary) |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +func TestContractRuntimeConfigShapeAndAuthMode(t *testing.T) { |
| 159 | + t.Setenv("AUTH_MODE", "disabled") |
| 160 | + t.Setenv("RSM_VERSION", "test-version") |
| 161 | + t.Setenv("RSM_COMMIT", "test-commit") |
| 162 | + h := newContractHarness(t) |
| 163 | + |
| 164 | + rr := h.request(http.MethodGet, "/api/runtime-config", nil) |
| 165 | + assertStatus(t, rr, http.StatusOK) |
| 166 | + assertJSONContentType(t, rr) |
| 167 | + |
| 168 | + body := decodeJSONMap(t, rr.Body) |
| 169 | + if !mustBool(t, body["success"], "success") { |
| 170 | + t.Fatalf("expected success=true") |
| 171 | + } |
| 172 | + runtime := mustObject(t, body["runtime"], "runtime") |
| 173 | + if mustString(t, runtime["appName"], "runtime.appName") != "RetroSaveManager" { |
| 174 | + t.Fatalf("unexpected app name: %s", prettyJSON(runtime)) |
| 175 | + } |
| 176 | + if got := mustString(t, runtime["authMode"], "runtime.authMode"); got != "disabled" { |
| 177 | + t.Fatalf("unexpected auth mode %q", got) |
| 178 | + } |
| 179 | + if mustBool(t, runtime["authEnabled"], "runtime.authEnabled") { |
| 180 | + t.Fatalf("expected authEnabled=false when AUTH_MODE=disabled") |
| 181 | + } |
| 182 | + if got := mustString(t, runtime["version"], "runtime.version"); got != "test-version" { |
| 183 | + t.Fatalf("unexpected version %q", got) |
| 184 | + } |
| 185 | + if got := mustString(t, runtime["commit"], "runtime.commit"); got != "test-commit" { |
| 186 | + t.Fatalf("unexpected commit %q", got) |
| 187 | + } |
| 188 | + features := mustObject(t, runtime["features"], "runtime.features") |
| 189 | + if !mustBool(t, features["selfHosted"], "features.selfHosted") { |
| 190 | + t.Fatalf("expected selfHosted feature") |
| 191 | + } |
| 192 | + if mustBool(t, features["publicSignup"], "features.publicSignup") { |
| 193 | + t.Fatalf("public signup should be disabled for self-host release") |
| 194 | + } |
| 195 | + warnings := mustArray(t, runtime["warnings"], "runtime.warnings") |
| 196 | + if len(warnings) == 0 { |
| 197 | + t.Fatalf("expected LAN-only warning when auth is disabled") |
| 198 | + } |
| 199 | +} |
| 200 | + |
| 201 | +func TestContractRuntimeConfigAuthEnabled(t *testing.T) { |
| 202 | + t.Setenv("AUTH_MODE", "enabled") |
| 203 | + h := newContractHarness(t) |
| 204 | + |
| 205 | + rr := h.request(http.MethodGet, "/runtime-config", nil) |
| 206 | + assertStatus(t, rr, http.StatusOK) |
| 207 | + body := decodeJSONMap(t, rr.Body) |
| 208 | + runtime := mustObject(t, body["runtime"], "runtime") |
| 209 | + if got := mustString(t, runtime["authMode"], "runtime.authMode"); got != "enabled" { |
| 210 | + t.Fatalf("unexpected auth mode %q", got) |
| 211 | + } |
| 212 | + if !mustBool(t, runtime["authEnabled"], "runtime.authEnabled") { |
| 213 | + t.Fatalf("expected authEnabled=true when AUTH_MODE=enabled") |
| 214 | + } |
| 215 | +} |
| 216 | + |
116 | 217 | func TestContractSaveLatestSkipsBrokenPayloadRecords(t *testing.T) { |
117 | 218 | h := newContractHarness(t) |
118 | 219 |
|
|
0 commit comments