Skip to content

Commit 753f039

Browse files
jwfingclaude
andcommitted
fix: align models with server responses and add integration tests
Fix DatabaseTableColumn, DatabaseTableForeignKey, FunctionMetadata, and DatabaseMetadata models to match actual server camelCase fields. Fix FunctionsClient.list_functions to handle wrapped response format. Add 15 integration tests covering auth, database CRUD, functions, metadata, and table admin. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 45d13d5 commit 753f039

8 files changed

Lines changed: 298 additions & 43 deletions

File tree

insforge/database/models.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,31 @@ class DatabaseQueryResponse(BaseModel):
1515
class DatabaseTableForeignKey(BaseModel):
1616
model_config = ConfigDict(extra="ignore", populate_by_name=True)
1717

18-
table: str
19-
column: str
20-
on_delete: str = Field(alias="on_delete")
18+
constraint_name: str | None = Field(default=None, alias="constraint_name")
19+
reference_table: str = Field(alias="referenceTable")
20+
reference_column: str = Field(alias="referenceColumn")
21+
on_delete: str = Field(default="NO ACTION", alias="onDelete")
22+
on_update: str | None = Field(default=None, alias="onUpdate")
2123

2224

2325
class DatabaseTableColumn(BaseModel):
2426
model_config = ConfigDict(extra="ignore", populate_by_name=True)
2527

26-
name: str
28+
name: str = Field(alias="columnName")
2729
type: str
28-
nullable: bool
29-
unique: bool
30-
default: str | None = None
30+
nullable: bool = Field(alias="isNullable")
31+
unique: bool = Field(alias="isUnique")
32+
default_value: str | None = Field(default=None, alias="defaultValue")
3133
is_primary_key: bool = Field(alias="isPrimaryKey")
3234
foreign_key: DatabaseTableForeignKey | None = Field(default=None, alias="foreignKey")
3335

3436

3537
class DatabaseTableSchemaResponse(BaseModel):
3638
model_config = ConfigDict(extra="ignore", populate_by_name=True)
3739

38-
table_name: str
40+
table_name: str = Field(alias="tableName")
3941
columns: list[DatabaseTableColumn] = Field(default_factory=list)
42+
record_count: int | str | None = Field(default=None, alias="recordCount")
4043

4144

4245
ColumnType = Literal["string", "datetime", "integer", "float", "boolean", "uuid", "json", "file"]

insforge/functions/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ async def list_functions(self, *, access_token: str | None = None) -> list[Funct
8686
"/api/functions",
8787
access_token=access_token,
8888
)
89-
return [FunctionMetadata.model_validate(item) for item in payload]
89+
items = payload.get("functions", []) if isinstance(payload, dict) else payload
90+
return [FunctionMetadata.model_validate(item) for item in items]
9091

9192
async def get_function(
9293
self,

insforge/functions/models.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@
1111

1212

1313
class FunctionMetadata(BaseModel):
14-
model_config = ConfigDict(extra="ignore")
14+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
1515

1616
id: str
1717
slug: str
1818
name: str
1919
description: str | None = None
2020
status: Literal["draft", "active", "error"]
21-
created_at: datetime
22-
updated_at: datetime
23-
deployed_at: datetime | None = None
21+
created_at: datetime = Field(alias="createdAt")
22+
updated_at: datetime = Field(alias="updatedAt")
23+
deployed_at: datetime | None = Field(default=None, alias="deployedAt")
2424

2525

2626
class FunctionDetails(FunctionMetadata):

insforge/metadata/models.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,16 @@ class AppMetadata(BaseModel):
3030
class DatabaseMetadataTable(BaseModel):
3131
model_config = ConfigDict(extra="ignore", populate_by_name=True)
3232

33-
name: str
33+
name: str = Field(alias="tableName")
3434
record_count: int = Field(alias="recordCount")
3535

3636

3737
class DatabaseMetadata(BaseModel):
3838
model_config = ConfigDict(extra="ignore", populate_by_name=True)
3939

4040
tables: list[DatabaseMetadataTable] = Field(default_factory=list)
41-
total_tables: int = Field(alias="totalTables")
42-
total_records: int = Field(alias="totalRecords")
43-
database_size: str = Field(alias="databaseSize")
44-
last_updated: datetime = Field(alias="lastUpdated")
41+
total_size_in_gb: float | None = Field(default=None, alias="totalSizeInGB")
42+
hint: str | None = None
4543

4644

4745
class ApiKeyMetadata(BaseModel):

tests/database/test_database_admin.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ async def fake_request(method: str, url: httpx.URL, **kwargs: object) -> httpx.R
5151
return httpx.Response(
5252
200,
5353
json={
54-
"table_name": "posts",
54+
"tableName": "posts",
5555
"columns": [
5656
{
57-
"name": "id",
57+
"columnName": "id",
5858
"type": "uuid",
59-
"nullable": False,
60-
"unique": True,
61-
"default": "gen_random_uuid()",
59+
"isNullable": False,
60+
"isUnique": True,
61+
"defaultValue": "gen_random_uuid()",
6262
"isPrimaryKey": True,
6363
"foreignKey": None,
6464
}
@@ -95,7 +95,7 @@ async def fake_request(method: str, url: httpx.URL, **kwargs: object) -> httpx.R
9595
captured["method"] = method
9696
captured["url"] = str(url)
9797
captured["kwargs"] = kwargs
98-
return httpx.Response(200, json={"table_name": "analytics/events", "columns": []})
98+
return httpx.Response(200, json={"tableName": "analytics/events", "columns": []})
9999

100100
async with InsforgeClient(
101101
base_url="https://example.com",

tests/functions/test_functions_client.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -138,18 +138,20 @@ async def fake_request(method: str, url: httpx.URL, **kwargs: object) -> httpx.R
138138
captured["kwargs"] = kwargs
139139
return httpx.Response(
140140
200,
141-
json=[
142-
{
143-
"id": "123e4567-e89b-12d3-a456-426614174000",
144-
"slug": "hello-world",
145-
"name": "Hello World Function",
146-
"description": "Returns a greeting message",
147-
"status": "active",
148-
"created_at": "2024-01-21T10:30:00Z",
149-
"updated_at": "2024-01-21T10:35:00Z",
150-
"deployed_at": "2024-01-21T10:35:00Z",
151-
}
152-
],
141+
json={
142+
"functions": [
143+
{
144+
"id": "123e4567-e89b-12d3-a456-426614174000",
145+
"slug": "hello-world",
146+
"name": "Hello World Function",
147+
"description": "Returns a greeting message",
148+
"status": "active",
149+
"createdAt": "2024-01-21T10:30:00Z",
150+
"updatedAt": "2024-01-21T10:35:00Z",
151+
"deployedAt": "2024-01-21T10:35:00Z",
152+
}
153+
],
154+
},
153155
)
154156

155157
async with InsforgeClient(

tests/metadata/test_metadata_client.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,10 @@ async def fake_request(method: str, url: httpx.URL, **kwargs: object) -> httpx.R
6464
200,
6565
json={
6666
"tables": [
67-
{"name": "posts", "recordCount": 42},
68-
{"name": "comments", "recordCount": 57},
67+
{"tableName": "posts", "recordCount": 42},
68+
{"tableName": "comments", "recordCount": 57},
6969
],
70-
"totalTables": 2,
71-
"totalRecords": 99,
72-
"databaseSize": "128 MB",
73-
"lastUpdated": "2026-03-28T00:00:00Z",
70+
"totalSizeInGB": 0.128,
7471
},
7572
)
7673

@@ -89,7 +86,7 @@ async def fake_request(method: str, url: httpx.URL, **kwargs: object) -> httpx.R
8986
assert captured["kwargs"]["headers"]["X-API-Key"] == "ins_test"
9087
assert captured["kwargs"]["headers"]["Authorization"] == "Bearer admin_token"
9188
assert isinstance(result, DatabaseMetadata)
92-
assert result.total_records == 99
89+
assert result.total_size_in_gb == 0.128
9390
assert result.tables[0].name == "posts"
9491

9592

0 commit comments

Comments
 (0)