diff --git a/conformance/runner/go/go.mod b/conformance/runner/go/go.mod index d89442fb..6120e741 100644 --- a/conformance/runner/go/go.mod +++ b/conformance/runner/go/go.mod @@ -1,6 +1,6 @@ module github.com/basecamp/basecamp-sdk/conformance/runner/go -go 1.26 +go 1.26.3 require github.com/basecamp/basecamp-sdk/go v0.0.0 diff --git a/go.work b/go.work index 758e6395..a2a64112 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.26 +go 1.26.3 use ( ./conformance/runner/go diff --git a/go/go.mod b/go/go.mod index ca78674d..8fb7a0d0 100644 --- a/go/go.mod +++ b/go/go.mod @@ -1,6 +1,6 @@ module github.com/basecamp/basecamp-sdk/go -go 1.26 +go 1.26.3 require ( github.com/oapi-codegen/runtime v1.4.0 diff --git a/go/pkg/basecamp/projects.go b/go/pkg/basecamp/projects.go index 8774c687..d3d1146a 100644 --- a/go/pkg/basecamp/projects.go +++ b/go/pkg/basecamp/projects.go @@ -18,6 +18,8 @@ type Project struct { Name string `json:"name"` Description string `json:"description"` Purpose string `json:"purpose"` + StartDate string `json:"start_date,omitempty"` + EndDate string `json:"end_date,omitempty"` ClientsEnabled bool `json:"clients_enabled"` BookmarkURL string `json:"bookmark_url"` URL string `json:"url"` @@ -374,6 +376,8 @@ func projectFromGenerated(gp generated.Project) Project { Name: gp.Name, Description: gp.Description, Purpose: gp.Purpose, + StartDate: gp.StartDate, + EndDate: gp.EndDate, ClientsEnabled: gp.ClientsEnabled, BookmarkURL: gp.BookmarkUrl, URL: gp.Url, diff --git a/go/pkg/basecamp/projects_test.go b/go/pkg/basecamp/projects_test.go index 0cc1d5e3..2872b589 100644 --- a/go/pkg/basecamp/projects_test.go +++ b/go/pkg/basecamp/projects_test.go @@ -52,6 +52,12 @@ func TestProject_UnmarshalList(t *testing.T) { if p1.Purpose != "topic" { t.Errorf("expected purpose 'topic', got %q", p1.Purpose) } + if p1.StartDate != "2022-01-01" { + t.Errorf("expected start_date '2022-01-01', got %q", p1.StartDate) + } + if p1.EndDate != "2022-04-01" { + t.Errorf("expected end_date '2022-04-01', got %q", p1.EndDate) + } if p1.ClientCompany != nil { t.Errorf("expected nil ClientCompany for first project") } @@ -98,6 +104,12 @@ func TestProject_UnmarshalGet(t *testing.T) { if project.Description != "Laptop product launch." { t.Errorf("expected description 'Laptop product launch.', got %q", project.Description) } + if project.StartDate != "2022-01-01" { + t.Errorf("expected start_date '2022-01-01', got %q", project.StartDate) + } + if project.EndDate != "2022-04-01" { + t.Errorf("expected end_date '2022-04-01', got %q", project.EndDate) + } if project.CreatedAt.IsZero() { t.Error("expected non-zero CreatedAt") } diff --git a/go/pkg/generated/client.gen.go b/go/pkg/generated/client.gen.go index 9b5334e5..7da5b459 100644 --- a/go/pkg/generated/client.gen.go +++ b/go/pkg/generated/client.gen.go @@ -1547,9 +1547,11 @@ type Project struct { CreatedAt time.Time `json:"created_at"` Description string `json:"description,omitempty"` Dock []DockItem `json:"dock,omitempty"` + EndDate string `json:"end_date,omitempty"` Id int64 `json:"id"` Name string `json:"name"` Purpose string `json:"purpose,omitempty"` + StartDate string `json:"start_date,omitempty"` // Status active|archived|trashed Status string `json:"status"` diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Project.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Project.kt index fa4f8a36..581755ae 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Project.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Project.kt @@ -21,6 +21,8 @@ data class Project( @SerialName("app_url") val appUrl: String, val description: String? = null, val purpose: String? = null, + @SerialName("start_date") val startDate: String? = null, + @SerialName("end_date") val endDate: String? = null, @SerialName("clients_enabled") val clientsEnabled: Boolean = false, @SerialName("bookmark_url") val bookmarkUrl: String? = null, val dock: List = emptyList(), diff --git a/kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/ProjectsServiceTest.kt b/kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/ProjectsServiceTest.kt index fcfa4754..c98d22b9 100644 --- a/kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/ProjectsServiceTest.kt +++ b/kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/ProjectsServiceTest.kt @@ -29,6 +29,7 @@ class ProjectsServiceTest { private fun projectJson(id: Long, name: String, description: String? = null) = """{ "id": $id, "status": "active", "name": "$name", "created_at": "2025-01-01T00:00:00Z", "updated_at": "2025-01-01T00:00:00Z", + "start_date": "2024-01-01", "end_date": "2024-03-31", "url": "https://3.basecampapi.com/12345/projects/$id.json", "app_url": "https://3.basecamp.com/12345/projects/$id", "dock": [] @@ -108,6 +109,8 @@ class ProjectsServiceTest { assertEquals(42L, project.id) assertEquals("My Project", project.name) assertEquals("A test project", project.description) + assertEquals("2024-01-01", project.startDate) + assertEquals("2024-03-31", project.endDate) client.close() } diff --git a/openapi.json b/openapi.json index 0c6282e8..fa6acfb8 100644 --- a/openapi.json +++ b/openapi.json @@ -25032,6 +25032,12 @@ "purpose": { "type": "string" }, + "start_date": { + "type": "string" + }, + "end_date": { + "type": "string" + }, "clients_enabled": { "type": "boolean" }, diff --git a/python/src/basecamp/generated/types.py b/python/src/basecamp/generated/types.py index b2946e5f..52311f3d 100644 --- a/python/src/basecamp/generated/types.py +++ b/python/src/basecamp/generated/types.py @@ -1002,9 +1002,11 @@ class Project(TypedDict): created_at: str description: NotRequired[str] dock: NotRequired[list[DockItem]] + end_date: NotRequired[str] id: int name: str purpose: NotRequired[str] + start_date: NotRequired[str] status: str updated_at: str url: str diff --git a/python/tests/services/test_projects.py b/python/tests/services/test_projects.py new file mode 100644 index 00000000..6233b9ba --- /dev/null +++ b/python/tests/services/test_projects.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import httpx +import respx + +from basecamp.client import Client + + +def make_account(): + client = Client(access_token="test-token") + return client, client.for_account("12345") + + +class TestProjects: + @respx.mock + def test_get_project_includes_schedule_dates(self): + respx.get("https://3.basecampapi.com/12345/projects/42").mock( + return_value=httpx.Response( + 200, + json={ + "id": 42, + "name": "My Project", + "status": "active", + "start_date": "2024-01-01", + "end_date": "2024-03-31", + "created_at": "2024-01-15T10:00:00Z", + "updated_at": "2024-01-15T10:00:00Z", + "url": "https://3.basecampapi.com/12345/projects/42.json", + "app_url": "https://3.basecamp.com/12345/projects/42", + }, + ) + ) + + client, account = make_account() + project = account.projects.get(project_id=42) + client.close() + + assert project["start_date"] == "2024-01-01" + assert project["end_date"] == "2024-03-31" + + @respx.mock + def test_list_projects_includes_schedule_dates(self): + respx.get("https://3.basecampapi.com/12345/projects.json").mock( + return_value=httpx.Response( + 200, + json=[ + { + "id": 1, + "name": "Project A", + "status": "active", + "start_date": "2024-01-01", + "end_date": "2024-03-31", + "created_at": "2024-01-15T10:00:00Z", + "updated_at": "2024-01-15T10:00:00Z", + "url": "https://3.basecampapi.com/12345/projects/1.json", + "app_url": "https://3.basecamp.com/12345/projects/1", + } + ], + ) + ) + + client, account = make_account() + projects = account.projects.list() + client.close() + + assert projects[0]["start_date"] == "2024-01-01" + assert projects[0]["end_date"] == "2024-03-31" diff --git a/ruby/lib/basecamp/generated/metadata.json b/ruby/lib/basecamp/generated/metadata.json index d5cd3625..313f2758 100644 --- a/ruby/lib/basecamp/generated/metadata.json +++ b/ruby/lib/basecamp/generated/metadata.json @@ -1,7 +1,7 @@ { "$schema": "https://basecamp.com/schemas/sdk-metadata.json", "version": "1.0.0", - "generated": "2026-04-29T18:03:49Z", + "generated": "2026-05-07T21:00:08Z", "operations": { "GetAccount": { "retry": { diff --git a/ruby/lib/basecamp/generated/types.rb b/ruby/lib/basecamp/generated/types.rb index 8cc6ed85..aff2b040 100644 --- a/ruby/lib/basecamp/generated/types.rb +++ b/ruby/lib/basecamp/generated/types.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Auto-generated from OpenAPI spec. Do not edit manually. -# Generated: 2026-04-29T18:03:49Z +# Generated: 2026-05-07T21:00:08Z require "json" require "time" @@ -2427,7 +2427,7 @@ def to_json(*args) # Project class Project include TypeHelpers - attr_accessor :app_url, :created_at, :id, :name, :status, :updated_at, :url, :bookmark_url, :bookmarked, :client_company, :clients_enabled, :clientside, :description, :dock, :purpose + attr_accessor :app_url, :created_at, :id, :name, :status, :updated_at, :url, :bookmark_url, :bookmarked, :client_company, :clients_enabled, :clientside, :description, :dock, :end_date, :purpose, :start_date # @return [Array] def self.required_fields @@ -2449,7 +2449,9 @@ def initialize(data = {}) @clientside = parse_type(data["clientside"], "ClientSide") @description = data["description"] @dock = parse_array(data["dock"], "DockItem") + @end_date = data["end_date"] @purpose = data["purpose"] + @start_date = data["start_date"] end def to_h @@ -2468,7 +2470,9 @@ def to_h "clientside" => @clientside, "description" => @description, "dock" => @dock, + "end_date" => @end_date, "purpose" => @purpose, + "start_date" => @start_date, }.compact end diff --git a/ruby/test/basecamp/services/projects_service_test.rb b/ruby/test/basecamp/services/projects_service_test.rb index 9b7bbffc..efc925ad 100644 --- a/ruby/test/basecamp/services/projects_service_test.rb +++ b/ruby/test/basecamp/services/projects_service_test.rb @@ -20,7 +20,9 @@ def sample_project(id: 123, name: "Test Project") "id" => id, "name" => name, "description" => "A test project", - "status" => "active" + "status" => "active", + "start_date" => "2024-01-01", + "end_date" => "2024-03-31" } end @@ -52,6 +54,8 @@ def test_get_project assert_equal 123, project["id"] assert_equal "Test Project", project["name"] + assert_equal "2024-01-01", project["start_date"] + assert_equal "2024-03-31", project["end_date"] end def test_create_project diff --git a/spec/basecamp.smithy b/spec/basecamp.smithy index f1db530b..ebf2d636 100644 --- a/spec/basecamp.smithy +++ b/spec/basecamp.smithy @@ -565,6 +565,8 @@ structure Project { name: ProjectName description: ProjectDescription purpose: String + start_date: ISO8601Date + end_date: ISO8601Date clients_enabled: Boolean bookmark_url: String @required diff --git a/spec/fixtures/projects/README.md b/spec/fixtures/projects/README.md index 0f68cc94..43e2ded2 100644 --- a/spec/fixtures/projects/README.md +++ b/spec/fixtures/projects/README.md @@ -37,7 +37,7 @@ JSON fixtures extracted from the canonical projects docs in `basecamp/bc3/doc/ap ## Notes -- list.json includes a project with `client_company` and `clientside` fields (id: 2085958500) -- get.json is a basic project without client fields +- get.json includes a scheduled project with `start_date` and `end_date` +- list.json includes one scheduled project and one project with `client_company` and `clientside` fields (id: 2085958500) - DockItem.position can be null when enabled=false - All timestamps are ISO8601 format diff --git a/spec/fixtures/projects/get.json b/spec/fixtures/projects/get.json index 4c0e1e4b..d9e18198 100644 --- a/spec/fixtures/projects/get.json +++ b/spec/fixtures/projects/get.json @@ -6,6 +6,8 @@ "name": "The Leto Laptop", "description": "Laptop product launch.", "purpose": "topic", + "start_date": "2022-01-01", + "end_date": "2022-04-01", "clients_enabled": false, "bookmark_url": "https://3.basecampapi.com/195539477/my/bookmarks/BAh7CEkiCGdpZAY6BkVUSSIrZ2lkOi8vYmMzL0J1Y2tldC8yMDg1OTU4NDk5P2V4cGlyZXNfaW4GOwBUSSIMcHVycG9zZQY7AFRJIg1yZWFkYWJsZQY7AFRJIg9leHBpcmVzX2F0BjsAVDA=--691d627098347705738640552798539681dcd3b6.json", "url": "https://3.basecampapi.com/195539477/projects/2085958499.json", diff --git a/spec/fixtures/projects/list.json b/spec/fixtures/projects/list.json index ee486f4c..0f13a574 100644 --- a/spec/fixtures/projects/list.json +++ b/spec/fixtures/projects/list.json @@ -7,6 +7,8 @@ "name": "The Leto Laptop", "description": "Laptop product launch.", "purpose": "topic", + "start_date": "2022-01-01", + "end_date": "2022-04-01", "clients_enabled": false, "bookmark_url": "https://3.basecampapi.com/195539477/my/bookmarks/BAh7CEkiCGdpZAY6BkVUSSIrZ2lkOi8vYmMzL0J1Y2tldC8yMDg1OTU4NDk5P2V4cGlyZXNfaW4GOwBUSSIMcHVycG9zZQY7AFRJIg1yZWFkYWJsZQY7AFRJIg9leHBpcmVzX2F0BjsAVDA=--691d627098347705738640552798539681dcd3b6.json", "url": "https://3.basecampapi.com/195539477/projects/2085958499.json", diff --git a/swift/Sources/Basecamp/Generated/Models/Project.swift b/swift/Sources/Basecamp/Generated/Models/Project.swift index da45c83d..f49afc3c 100644 --- a/swift/Sources/Basecamp/Generated/Models/Project.swift +++ b/swift/Sources/Basecamp/Generated/Models/Project.swift @@ -16,7 +16,9 @@ public struct Project: Codable, Sendable { public var clientside: ClientSide? public var description: String? public var dock: [DockItem]? + public var endDate: String? public var purpose: String? + public var startDate: String? public init( appUrl: String, @@ -33,7 +35,9 @@ public struct Project: Codable, Sendable { clientside: ClientSide? = nil, description: String? = nil, dock: [DockItem]? = nil, - purpose: String? = nil + endDate: String? = nil, + purpose: String? = nil, + startDate: String? = nil ) { self.appUrl = appUrl self.createdAt = createdAt @@ -49,6 +53,8 @@ public struct Project: Codable, Sendable { self.clientside = clientside self.description = description self.dock = dock + self.endDate = endDate self.purpose = purpose + self.startDate = startDate } } diff --git a/swift/Tests/BasecampTests/GeneratedServiceTests.swift b/swift/Tests/BasecampTests/GeneratedServiceTests.swift index d893454a..84a7b240 100644 --- a/swift/Tests/BasecampTests/GeneratedServiceTests.swift +++ b/swift/Tests/BasecampTests/GeneratedServiceTests.swift @@ -13,6 +13,7 @@ final class GeneratedServiceTests: XCTestCase { "id": 42, "name": "My Project", "status": "active", "app_url": "https://3.basecamp.com/1/projects/42", "url": "https://3.basecampapi.com/1/projects/42.json", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z", + "start_date": "2024-01-01", "end_date": "2024-03-31", ] let data = try JSONSerialization.data(withJSONObject: json) @@ -23,6 +24,8 @@ final class GeneratedServiceTests: XCTestCase { XCTAssertEqual(project.id, 42) XCTAssertEqual(project.name, "My Project") XCTAssertEqual(project.status, "active") + XCTAssertEqual(project.startDate, "2024-01-01") + XCTAssertEqual(project.endDate, "2024-03-31") // Verify request was sent to the correct path let lastURL = transport.lastRequest!.request.url!.absoluteString @@ -79,7 +82,8 @@ final class GeneratedServiceTests: XCTestCase { let projects: [[String: Any]] = [ ["id": 1, "name": "Project A", "status": "active", "app_url": "https://3.basecamp.com/1/projects/1", "url": "https://3.basecampapi.com/1/projects/1.json", - "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z"], + "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z", + "start_date": "2024-01-01", "end_date": "2024-03-31"], ["id": 2, "name": "Project B", "status": "active", "app_url": "https://3.basecamp.com/1/projects/2", "url": "https://3.basecampapi.com/1/projects/2.json", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z"], @@ -93,6 +97,7 @@ final class GeneratedServiceTests: XCTestCase { let result = try await account.projects.list() XCTAssertEqual(result.count, 2) XCTAssertEqual(result[0].id, 1) + XCTAssertEqual(result[0].startDate, "2024-01-01") XCTAssertEqual(result[1].name, "Project B") XCTAssertEqual(result.meta.totalCount, 2) } diff --git a/typescript/src/generated/metadata.json b/typescript/src/generated/metadata.json index 940586b0..1b2f7732 100644 --- a/typescript/src/generated/metadata.json +++ b/typescript/src/generated/metadata.json @@ -1,7 +1,7 @@ { "$schema": "https://basecamp.com/schemas/sdk-metadata.json", "version": "1.0.0", - "generated": "2026-04-29T18:03:48.821Z", + "generated": "2026-05-07T21:00:07.713Z", "operations": { "GetAccount": { "retry": { diff --git a/typescript/src/generated/openapi-stripped.json b/typescript/src/generated/openapi-stripped.json index 496f978d..f647dab7 100644 --- a/typescript/src/generated/openapi-stripped.json +++ b/typescript/src/generated/openapi-stripped.json @@ -22708,6 +22708,12 @@ "purpose": { "type": "string" }, + "start_date": { + "type": "string" + }, + "end_date": { + "type": "string" + }, "clients_enabled": { "type": "boolean" }, diff --git a/typescript/src/generated/schema.d.ts b/typescript/src/generated/schema.d.ts index ad185afd..7c36fb33 100644 --- a/typescript/src/generated/schema.d.ts +++ b/typescript/src/generated/schema.d.ts @@ -3678,6 +3678,8 @@ export interface components { name: string; description?: string; purpose?: string; + start_date?: string; + end_date?: string; clients_enabled?: boolean; bookmark_url?: string; url: string; diff --git a/typescript/tests/services/projects.test.ts b/typescript/tests/services/projects.test.ts index c6dfb358..688eb429 100644 --- a/typescript/tests/services/projects.test.ts +++ b/typescript/tests/services/projects.test.ts @@ -15,6 +15,8 @@ const sampleProject = (id = 1) => ({ name: "My Project", description: "

A cool project

", status: "active", + start_date: "2024-01-01", + end_date: "2024-03-31", created_at: "2024-01-15T10:00:00Z", updated_at: "2024-01-15T10:00:00Z", }); @@ -41,6 +43,7 @@ describe("ProjectsService", () => { const projects = await client.projects.list(); expect(projects).toHaveLength(2); expect(projects[0]!.id).toBe(1); + expect(projects[0]!.start_date).toBe("2024-01-01"); expect(projects[1]!.id).toBe(2); }); @@ -69,6 +72,8 @@ describe("ProjectsService", () => { const project = await client.projects.get(projectId); expect(project.id).toBe(projectId); expect(project.name).toBe("My Project"); + expect(project.start_date).toBe("2024-01-01"); + expect(project.end_date).toBe("2024-03-31"); }); it("should throw not_found for missing project", async () => {