Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion conformance/runner/go/go.mod
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion go.work
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
go 1.26
go 1.26.3

use (
./conformance/runner/go
Expand Down
2 changes: 1 addition & 1 deletion go/go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 4 additions & 0 deletions go/pkg/basecamp/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 12 additions & 0 deletions go/pkg/basecamp/projects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down Expand Up @@ -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")
}
Expand Down
2 changes: 2 additions & 0 deletions go/pkg/generated/client.gen.go

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
Expand Up @@ -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<DockItem> = emptyList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
Expand Down Expand Up @@ -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()
}
Expand Down
6 changes: 6 additions & 0 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -25032,6 +25032,12 @@
"purpose": {
"type": "string"
},
"start_date": {
"type": "string"
},
"end_date": {
"type": "string"
},
"clients_enabled": {
"type": "boolean"
},
Expand Down
2 changes: 2 additions & 0 deletions python/src/basecamp/generated/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
67 changes: 67 additions & 0 deletions python/tests/services/test_projects.py
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 1 addition & 1 deletion ruby/lib/basecamp/generated/metadata.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
8 changes: 6 additions & 2 deletions ruby/lib/basecamp/generated/types.rb
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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<Symbol>]
def self.required_fields
Expand All @@ -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
Expand All @@ -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

Expand Down
6 changes: 5 additions & 1 deletion ruby/test/basecamp/services/projects_service_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions spec/basecamp.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions spec/fixtures/projects/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions spec/fixtures/projects/get.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions spec/fixtures/projects/list.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion swift/Sources/Basecamp/Generated/Models/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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
}
}
7 changes: 6 additions & 1 deletion swift/Tests/BasecampTests/GeneratedServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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
Expand Down Expand Up @@ -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"],
Expand All @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion typescript/src/generated/metadata.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
6 changes: 6 additions & 0 deletions typescript/src/generated/openapi-stripped.json
Original file line number Diff line number Diff line change
Expand Up @@ -22708,6 +22708,12 @@
"purpose": {
"type": "string"
},
"start_date": {
"type": "string"
},
"end_date": {
"type": "string"
},
"clients_enabled": {
"type": "boolean"
},
Expand Down
2 changes: 2 additions & 0 deletions typescript/src/generated/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading