-
Notifications
You must be signed in to change notification settings - Fork 11
Absorb external-links (doors) list surface; defer create/image/composite (bc3 #12375) #431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: bubble-ups-surface
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -182,5 +182,19 @@ | |
| { "type": "liveCallSucceeds" }, | ||
| { "type": "liveSchemaValidate" } | ||
| ] | ||
| }, | ||
| { | ||
| "mode": "live", | ||
| "name": "ListRecordings type=Door decodes the external-link (door) shape", | ||
| "description": "DECODING coverage for the absorbed external-links/doors list surface (spec/api-gaps/external-links-doors.md, bc3 #12375). Drives the type=Door recordings query and validates each recording against the Recording schema, including the door-specific url/service/description/position fields. Live-dormant: validates statically until credentials are provisioned. Create/image/composite remain residual gaps (see the entry).", | ||
| "operation": "ListRecordings", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: This new live fixture adds Prompt for AI agents |
||
| "method": "GET", | ||
| "path": "/projects/recordings.json", | ||
| "queryParams": { "type": "Door" }, | ||
| "tags": ["live", "read-only", "bc5-additive"], | ||
| "liveAssertions": [ | ||
| { "type": "liveCallSucceeds" }, | ||
| { "type": "liveSchemaValidate" } | ||
| ] | ||
| } | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -268,6 +268,7 @@ func TestRecordingType_Constants(t *testing.T) { | |
| }{ | ||
| {RecordingTypeComment, "Comment"}, | ||
| {RecordingTypeDocument, "Document"}, | ||
| {RecordingTypeDoor, "Door"}, | ||
| {RecordingTypeKanbanCard, "Kanban::Card"}, | ||
| {RecordingTypeKanbanStep, "Kanban::Step"}, | ||
| {RecordingTypeMessage, "Message"}, | ||
|
|
@@ -286,6 +287,86 @@ func TestRecordingType_Constants(t *testing.T) { | |
| } | ||
| } | ||
|
|
||
| // TestRecording_UnmarshalDoor verifies that a type=Door recording (external | ||
| // link) decodes with the full door shape — the outside url, the service struct, | ||
| // the description, and the position — through the shared Recording projection. | ||
| func TestRecording_UnmarshalDoor(t *testing.T) { | ||
| data := `[ | ||
| { | ||
| "id": 1069480290, | ||
| "status": "active", | ||
| "visible_to_clients": false, | ||
| "created_at": "2026-07-22T15:51:54.872Z", | ||
| "updated_at": "2026-07-22T15:51:54.886Z", | ||
| "title": "Design system", | ||
| "inherits_status": true, | ||
| "type": "Door", | ||
| "url": "https://www.figma.com/file/abc123/Design-system", | ||
| "app_url": "https://3.basecampapi.com/195539477/buckets/2085958504/dock/doors/1069480290", | ||
| "position": 8, | ||
| "bucket": {"id": 2085958504, "name": "The Leto Laptop", "type": "Project"}, | ||
| "creator": {"id": 1049715913, "name": "Victor Cooper"}, | ||
| "service": { | ||
| "name": "Figma", | ||
| "example_url": "https://www.figma.com/file/aGVsbG8gZmlnbWEgZmlsZQ", | ||
| "code": "figma", | ||
| "valid_patterns": ["(.*?\\.)?figma\\.com(\\/.*)?"], | ||
| "supporting_text": "a file or project on Figma" | ||
| }, | ||
| "description": "<div>Shared Figma workspace</div>" | ||
| } | ||
| ]` | ||
|
|
||
| var recordings []Recording | ||
| if err := json.Unmarshal([]byte(data), &recordings); err != nil { | ||
| t.Fatalf("failed to unmarshal door recording: %v", err) | ||
| } | ||
| if len(recordings) != 1 { | ||
| t.Fatalf("expected 1 recording, got %d", len(recordings)) | ||
| } | ||
| d := recordings[0] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: TestRecording_UnmarshalDoor includes "example_url" in the service fixture but never asserts on d.Service.ExampleURL. The test comment says it verifies "the full door shape" and other service fields (Name, Code, ValidPatterns, SupportingText) are checked, so omitting ExampleURL means a regression in its decoding wouldn't be caught. Prompt for AI agents |
||
| if d.Type != "Door" { | ||
| t.Errorf("expected type Door, got %q", d.Type) | ||
| } | ||
| if d.URL != "https://www.figma.com/file/abc123/Design-system" { | ||
| t.Errorf("expected external url, got %q", d.URL) | ||
| } | ||
| if d.Position != 8 { | ||
| t.Errorf("expected position 8, got %d", d.Position) | ||
| } | ||
| if d.Description != "<div>Shared Figma workspace</div>" { | ||
| t.Errorf("unexpected description: %q", d.Description) | ||
| } | ||
| if d.Service == nil { | ||
| t.Fatal("expected service struct to be non-nil for a Door") | ||
| } | ||
| if d.Service.Name != "Figma" || d.Service.Code != "figma" { | ||
| t.Errorf("unexpected service name/code: %q/%q", d.Service.Name, d.Service.Code) | ||
| } | ||
| if len(d.Service.ValidPatterns) != 1 || d.Service.ValidPatterns[0] == "" { | ||
| t.Errorf("expected 1 valid_pattern, got %v", d.Service.ValidPatterns) | ||
| } | ||
| if d.Service.SupportingText != "a file or project on Figma" { | ||
| t.Errorf("unexpected supporting_text: %q", d.Service.SupportingText) | ||
| } | ||
| } | ||
|
|
||
| // TestRecording_UnmarshalNonDoorOmitsDoorFields verifies a non-door recording | ||
| // leaves the door-specific fields empty (they are optional). | ||
| func TestRecording_UnmarshalNonDoorOmitsDoorFields(t *testing.T) { | ||
| data := `{"id": 1, "type": "Message", "title": "Hi", "url": "https://x/1.json"}` | ||
| var r Recording | ||
| if err := json.Unmarshal([]byte(data), &r); err != nil { | ||
| t.Fatalf("failed to unmarshal: %v", err) | ||
| } | ||
| if r.Service != nil { | ||
| t.Errorf("expected nil service for non-door, got %+v", r.Service) | ||
| } | ||
| if r.Description != "" || r.Position != 0 { | ||
| t.Errorf("expected empty door fields, got description=%q position=%d", r.Description, r.Position) | ||
| } | ||
| } | ||
|
|
||
| func TestRecordingsListOptions_BuildsQueryParams(t *testing.T) { | ||
| // This is a structural test to ensure the options fields exist | ||
| opts := RecordingsListOptions{ | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.basecamp.sdk.generated.models | ||
|
|
||
| import kotlinx.serialization.SerialName | ||
| import kotlinx.serialization.Serializable | ||
| import kotlinx.serialization.json.JsonElement | ||
| import kotlinx.serialization.json.JsonObject | ||
|
|
||
| /** | ||
| * DoorService entity from the Basecamp API. | ||
| * | ||
| * @generated from OpenAPI spec — do not edit directly | ||
| */ | ||
| @Serializable | ||
| data class DoorService( | ||
| val name: String? = null, | ||
| @SerialName("example_url") val exampleUrl: String? = null, | ||
| val code: String? = null, | ||
| @SerialName("valid_patterns") val validPatterns: List<String> = emptyList(), | ||
| @SerialName("supporting_text") val supportingText: String? = null | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the live canary runs with
BASECAMP_LIVE=1,live-runner.test.tspasses every fixture operation toassertDispatchCoverage, butLIVE_OPERATIONShas noListRecordingsentry. The runner therefore aborts inbeforeAllwith “missing dispatch cases for: ListRecordings” before issuing any request, so this new Door field coverage can never execute; add a dispatch that callsctx.client.recordings.list("Door", ...).AGENTS.md reference: AGENTS.md:L310-L313
Useful? React with 👍 / 👎.