(t *testing.T)
| 340 | } |
| 341 | |
| 342 | func TestPermissionErrorEnvelopeShape(t *testing.T) { |
| 343 | resp := map[string]any{ |
| 344 | "code": 99991679, |
| 345 | "msg": "missing scope", |
| 346 | "log_id": "lg-1", |
| 347 | "error": map[string]any{ |
| 348 | "permission_violations": []any{ |
| 349 | map[string]any{"subject": "docx:document"}, |
| 350 | }, |
| 351 | }, |
| 352 | } |
| 353 | err := errclass.BuildAPIError(resp, errclass.ClassifyContext{Brand: "feishu", AppID: "cli_a123", Identity: "user"}) |
| 354 | |
| 355 | var buf bytes.Buffer |
| 356 | ok := output.WriteTypedErrorEnvelope(&buf, err, "user") |
| 357 | if !ok { |
| 358 | t.Fatal("WriteTypedErrorEnvelope returned false for typed error") |
| 359 | } |
| 360 | out := buf.String() |
| 361 | |
| 362 | // positive assertions |
| 363 | for _, want := range []string{ |
| 364 | `"type": "authorization"`, |
| 365 | `"subtype": "missing_scope"`, |
| 366 | `"code": 99991679`, |
| 367 | `"missing_scopes":`, |
| 368 | `"docx:document"`, |
| 369 | `"identity": "user"`, |
| 370 | `"log_id": "lg-1"`, |
| 371 | } { |
| 372 | if !strings.Contains(out, want) { |
| 373 | t.Errorf("envelope missing %q\nfull: %s", want, out) |
| 374 | } |
| 375 | } |
| 376 | // negative assertions on the wire format |
| 377 | for _, mustNot := range []string{ |
| 378 | `"component"`, |
| 379 | `"doc_url"`, |
| 380 | `"retryable":`, // Retryable defaults false, omitempty → key absent |
| 381 | // console_url is gated to SubtypeAppScopeNotApplied (bot-perspective |
| 382 | // dev-action recovery). For user-perspective missing_scope the only |
| 383 | // actionable recovery is `lark-cli auth login --scope ...` (already |
| 384 | // in Hint), so the URL is dropped from the wire to avoid pointing an |
| 385 | // end user at a console they cannot modify. |
| 386 | `"console_url":`, |
| 387 | } { |
| 388 | if strings.Contains(out, mustNot) { |
| 389 | t.Errorf("envelope must not contain %q\nfull: %s", mustNot, out) |
| 390 | } |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | func TestRetryableEnvelope_TrueOnly(t *testing.T) { |
| 395 | // Test 1: Retryable:true → key present |
nothing calls this directly
no test coverage detected