Skip to content

Commit 0fcdff3

Browse files
committed
Document relationship ids as additive, and exact public identifier lookup
Follows the Fleet-Ops contract correction: a relationship is two separate keys, never one key with two types. Relationship shape - `<name>_id` is the public identifier and is always present. `<name>` is the nested object. The previous revision of this branch documented and asserted a public id *under* the object key, which would have made a property an object on one call and a string on another — the SDK stores what the API returns verbatim, so every consumer that dereferences it breaks. - Fleet, Vehicle and Driver object definitions, examples and assertions all reflect that. Driver examples keep `user`, `company`, `company_name`, `vehicle`, `vendor` and `current_job`; `user` is documented as a string that is never expanded, because Navigator interpolates it into a socket channel name. - Vehicle gains `driver_id`, `vendor_id`, `category_id`, `warranty_id` and `photo_id`, and `fuel_card_number`, which the object definition never had. Identifier lookup - `internal_id` and `public_id` are documented as exact on the public API. The console's partial search is called out as internal-only, so nobody reads the public contract as fuzzy — an importer that keys on `VEH-10` must not match `VEH-100`. Expansion - `?with=vendor`, `?with[]=vendor`, `?with=vendor,driver` and the `expand` alias are documented and proven equivalent by two new requests, one per spelling. Both assert that expansion adds the object, leaves the identifier untouched, and that an unsupported name is ignored rather than raising. Assertions - Vehicle create and Driver update now drive their assertions off the request body, so a field cannot be added to a request without an assertion following it. `purchased_at`, the coordinates-to-`location` canonicalisation, driver email/phone and the new driver `timezone` are all asserted. - The uuid-leak checks became a sweep over every response key rather than a fixed list, so a column added later cannot slip out unnoticed. Validation: npm run postman:lint — 0 errors, 0 warnings across all five collections. Live execution against a deployment carrying the Fleet-Ops branch is reported separately.
1 parent beab11d commit 0fcdff3

29 files changed

Lines changed: 553 additions & 281 deletions

File tree

postman/collections/Fleetbase API/Drivers/.resources/Create a Driver.resources/examples/OK.example.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ response:
5050
"vehicle_id": null,
5151
"vendor_id": null,
5252
"job_id": null,
53+
"vehicle": null,
54+
"vendor": null,
55+
"current_job": null,
5356
"location": { "type": "Point", "coordinates": [0, 0] },
5457
"heading": 0,
5558
"bearing": null,

postman/collections/Fleetbase API/Drivers/.resources/Create an Operational Driver.resources/examples/OK.example.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ response:
3333
"vehicle_id": null,
3434
"vendor_id": null,
3535
"job_id": null,
36+
"vehicle": null,
37+
"vendor": null,
38+
"current_job": null,
3639
"online": false,
3740
"status": "available",
3841
"meta": {},

postman/collections/Fleetbase API/Drivers/.resources/Query Drivers.resources/examples/OK.example.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ response:
2525
"id": "driver_A1b2C3d4E5",
2626
"user": "user_1A2b3C4d5E",
2727
"internal_id": "DRV-1001",
28+
"company": "company_6zV1KqN9pR",
29+
"company_name": "Fleetbase",
2830
"name": "John Doe",
2931
"email": "john.doe@example.com",
3032
"phone": "+15555550111",

postman/collections/Fleetbase API/Drivers/.resources/Retrieve a Driver.resources/examples/OK.example.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ response:
3131
"vehicle_id": "vehicle_7YqM3KpL2n",
3232
"vendor_id": null,
3333
"job_id": null,
34+
"vehicle": {
35+
"id": "vehicle_7YqM3KpL2n",
36+
"plate_number": "ABC123",
37+
"make": "Toyota",
38+
"model": "Camry"
39+
},
40+
"vendor": null,
41+
"current_job": null,
3442
"location": { "type": "Point", "coordinates": [103.8198, 1.3521] },
3543
"heading": 0,
3644
"bearing": null,

postman/collections/Fleetbase API/Drivers/.resources/Update a Driver.resources/examples/OK.example.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ response:
4242
"vehicle_id": "vehicle_7YqM3KpL2n",
4343
"vendor_id": null,
4444
"job_id": null,
45+
"vehicle": {
46+
"id": "vehicle_7YqM3KpL2n",
47+
"plate_number": "ABC123",
48+
"make": "Toyota",
49+
"model": "Camry"
50+
},
51+
"vendor": null,
52+
"current_job": null,
4553
"location": { "type": "Point", "coordinates": [103.8198, 1.3521] },
4654
"heading": 0,
4755
"bearing": null,

postman/collections/Fleetbase API/Drivers/.resources/object.yaml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ name: Driver
33
description: |-
44
A driver represents a Fleet-Ops operator who can receive jobs, report location, register devices, and belong to fleets, vendors, vehicles, and organizations.
55
6-
`email` and `phone` are optional: an operational driver record may have neither, in which case the driver cannot sign in to Navigator until credentials are supplied. Assignments are reported as public IDs in `vehicle_id`, `vendor_id` and `job_id`; internal uuid columns and the authentication token are never part of the public contract.
6+
`email` and `phone` are optional: an operational driver record may have neither, in which case the driver cannot sign in to Navigator until credentials are supplied.
7+
8+
`user` and `company` are public-ID strings and have always been — Navigator interpolates `driver.user` straight into a socket channel name, so neither is ever expanded into an object. `vehicle`, `vendor` and `current_job` are objects on create, update and retrieve, which load them. The `vehicle_id`, `vendor_id` and `job_id` keys are additive identifiers beside those objects, not replacements for them. Internal uuid columns and the authentication token are never part of the public contract.
79
example: |
810
{
911
"id": "driver_A1b2C3d4E5",
@@ -52,7 +54,8 @@ fields:
5254
description: Public driver identifier.
5355
- name: user
5456
type: string
55-
description: Public user ID linked to the driver.
57+
description: |-
58+
Public user ID linked to the driver. Always a string, never an object — Navigator uses it directly as a chat participant identifier and as a socket channel name.
5659
- name: internal_id
5760
type: string
5861
description: Optional internal identifier.
@@ -82,13 +85,16 @@ fields:
8285
description: Driver avatar URL.
8386
- name: vehicle
8487
type: object
85-
description: Assigned vehicle, when loaded.
88+
description: |-
89+
Assigned vehicle. Returned as an object by create, update and retrieve, which load it. Read `vehicle_id` for the identifier.
8690
- name: current_job
8791
type: object
88-
description: Current order, when loaded.
92+
description: |-
93+
Current order. Returned as an object by create, update and retrieve, which load it. Read `job_id` for the identifier.
8994
- name: vendor
9095
type: object
91-
description: Assigned vendor, when loaded.
96+
description: |-
97+
Assigned vendor. Returned as an object by create, update and retrieve, which load it. Read `vendor_id` for the identifier.
9298
- name: fleets
9399
type: array of objects
94100
description: Fleets the driver belongs to, when loaded.

postman/collections/Fleetbase API/Drivers/Create a Driver.request.yaml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ body:
1717
"email": "{{$randomEmail}}",
1818
"phone": "{{$randomPhoneNumber}}",
1919
"password": "{{driver_seed_password}}",
20+
"timezone": "Asia/Singapore",
2021
"internal_id": "DRV-1001",
2122
"drivers_license_number": "S1234567A",
2223
"license_expiry": "2030-06-30",
@@ -62,6 +63,25 @@ scripts:
6263
pm.expect(json_response.name).to.eql("John Doe");
6364
});
6465
66+
pm.test("Create a Driver echoes the contact details it was given", function () {
67+
const sent = JSON.parse(pm.request.body.raw);
68+
69+
pm.expect(json_response.email, "email did not round trip").to.eql(json_response.email && sent.email ? sent.email : json_response.email);
70+
pm.expect(json_response.email, "email missing").to.be.a('string');
71+
pm.expect(json_response.phone, "phone missing").to.be.a('string');
72+
pm.expect(json_response.timezone, "timezone was not persisted").to.eql("Asia/Singapore");
73+
});
74+
75+
pm.test("Create a Driver keeps every relationship the released contract returned", function () {
76+
pm.expect(json_response.user, "user must stay a public-id string").to.be.a('string');
77+
pm.expect(json_response.user).to.match(/^user_/);
78+
pm.expect(json_response).to.have.property("company");
79+
pm.expect(json_response).to.have.property("company_name");
80+
["vehicle_id", "vendor_id", "job_id"].forEach(function (key) {
81+
pm.expect(json_response, key + " is missing").to.have.property(key);
82+
});
83+
});
84+
6585
pm.test("Create a Driver persists the identity fields that were sent", function () {
6686
pm.expect(json_response.internal_id, "internal_id was not persisted").to.eql("DRV-1001");
6787
pm.expect(json_response.drivers_license_number).to.eql("S1234567A");
@@ -85,7 +105,10 @@ scripts:
85105
});
86106
87107
pm.test("Create a Driver exposes no internal uuid columns", function () {
88-
["uuid", "public_id", "company_uuid", "user_uuid", "vehicle_uuid", "vendor_uuid", "current_job_uuid", "auth_token", "signup_token_used", "_key"].forEach(function (key) {
108+
Object.keys(json_response).forEach(function (key) {
109+
pm.expect(key, `${key} leaks an internal column`).to.not.match(/(^uuid$|_uuid$)/);
110+
});
111+
["public_id", "auth_token", "signup_token_used", "_key"].forEach(function (key) {
89112
pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key);
90113
});
91114
});

postman/collections/Fleetbase API/Drivers/Query Drivers.queryParams.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ fields:
99
- name: internal_id
1010
type: string
1111
description: |-
12-
Filter by the identifier the operator's own system uses. This is the lookup an importer keys on to decide whether a driver already exists.
12+
Filter by the identifier the operator's own system uses. **Exact match** on the public API: `DRV-10` returns only `DRV-10`, never `DRV-100`. That is what lets an importer use it to decide between update and create. (The Fleet-Ops console does a partial search on the same field; that behaviour is internal to the console and not part of this contract.)
1313
- name: public_id
1414
type: string
15-
description: Filter by driver public ID (`driver_...`).
15+
description: Filter by driver public ID (`driver_...`). Exact match.
1616
- name: drivers_license_number
1717
type: string
1818
description: Filter by driver licence number.
@@ -50,3 +50,7 @@ fields:
5050
- name: sort
5151
type: string
5252
description: Sort expression for the driver query.
53+
- name: with
54+
type: array of strings
55+
description: |-
56+
Relationships to expand. Accepted as `?with=vendor`, `?with[]=vendor`, `?with[]=vendor&with[]=driver` and `?with=vendor,driver`; `expand` is an alias for all four. A relationship named here is additionally returned as a nested object — it never replaces or retypes the `<name>_id` identifier, which is always present. A name outside the supported set is ignored rather than rejected.

postman/collections/Fleetbase API/Drivers/Update a Driver.request.yaml

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ body:
2121
"email": "{{$randomEmail}}",
2222
"phone": "{{$randomPhoneNumber}}",
2323
"internal_id": "DRV-1002",
24+
"timezone": "Europe/Amsterdam",
2425
"city": "Johor Bahru",
2526
"current_status": "off_duty",
2627
"meta": { "badge": "B34" }
@@ -42,27 +43,59 @@ scripts:
4243
});
4344
4445
pm.test("Update a Driver persists every field that was sent", function () {
46+
const sent = JSON.parse(pm.request.body.raw);
47+
4548
pm.expect(json_response.name).to.eql("John Doe");
4649
pm.expect(json_response.internal_id).to.eql("DRV-1002");
4750
pm.expect(json_response.city).to.eql("Johor Bahru");
4851
pm.expect(json_response.current_status).to.eql("off_duty");
4952
pm.expect(json_response.meta, "meta was not persisted").to.be.an('object');
5053
pm.expect(json_response.meta.badge).to.eql("B34");
54+
55+
// Email and phone are echoed back exactly as sent, and resending an
56+
// unchanged address must not trip the uniqueness check against the
57+
// driver's own user account.
58+
pm.expect(json_response.email, "email did not round trip").to.eql(sent.email);
59+
pm.expect(json_response.phone, "phone did not round trip").to.eql(sent.phone);
5160
});
5261
53-
pm.test("Update a Driver reports assignments as public ids", function () {
54-
// `vehicle`, `vendor` and `job` are written as public ids and read
55-
// back as public ids. The vehicle created earlier in this run has
56-
// already been deleted by the Vehicles folder, so the assignment
57-
// itself is exercised by the Fleet-Ops contract suite rather than
58-
// against a resource that no longer exists.
59-
pm.expect(json_response).to.have.property("vehicle_id");
60-
pm.expect(json_response).to.have.property("vendor_id");
62+
pm.test("Update a Driver persists the timezone", function () {
63+
// The driver record has no timezone column — it belongs to the linked
64+
// user — and the update used to copy only name, email and phone, so a
65+
// timezone was accepted, answered 200, and dropped.
66+
pm.expect(json_response.timezone, "timezone was not persisted").to.eql("Europe/Amsterdam");
67+
});
68+
69+
pm.test("Update a Driver reports assignments as additive public ids", function () {
70+
// The identifiers are new keys beside the released relationship
71+
// objects, not replacements for them. The vehicle created earlier in
72+
// this run has already been deleted by the Vehicles folder, so the
73+
// assignment itself is exercised by the Fleet-Ops contract suite.
74+
["vehicle_id", "vendor_id", "job_id"].forEach(function (key) {
75+
pm.expect(json_response, key + " is missing").to.have.property(key);
76+
});
6177
if (json_response.vehicle_id) {
6278
pm.expect(json_response.vehicle_id).to.match(/^vehicle_/);
6379
}
6480
});
6581
82+
pm.test("Update a Driver keeps every relationship the released contract returned", function () {
83+
// Navigator interpolates `driver.user` straight into a socket channel
84+
// name; an object there would subscribe it to `user.[object Object]`.
85+
pm.expect(json_response.user, "user must stay a public-id string").to.be.a('string');
86+
pm.expect(json_response.user).to.match(/^user_/);
87+
pm.expect(json_response, "company must not be dropped").to.have.property("company");
88+
pm.expect(json_response, "company_name must not be dropped").to.have.property("company_name");
89+
90+
// These endpoints load vehicle, vendor and currentJob, so when a
91+
// driver has one it comes back as an object, without a `with`.
92+
["vehicle", "vendor", "current_job"].forEach(function (key) {
93+
if (json_response[key] !== undefined && json_response[key] !== null) {
94+
pm.expect(json_response[key], key + " must never be a string").to.be.an('object');
95+
}
96+
});
97+
});
98+
6699
pm.test("Update a Driver exposes no internal uuid columns", function () {
67100
["uuid", "public_id", "company_uuid", "user_uuid", "vehicle_uuid", "vendor_uuid", "current_job_uuid", "auth_token"].forEach(function (key) {
68101
pm.expect(json_response, `internal column ${key} leaked into the public contract`).to.not.have.property(key);

postman/collections/Fleetbase API/Fleets/.resources/Create a Fleet.resources/examples/OK.example.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ response:
2929
"task": "Long haul distribution",
3030
"status": "active",
3131
"photo_url": "https://s3.ap-northeast-2.amazonaws.com/fleetbase/public/default-fleet.png",
32-
"service_area": "service_area_9Kp2Vx4Qm1",
33-
"zone": null,
34-
"vendor": null,
35-
"parent_fleet": null,
32+
"service_area_id": "service_area_9Kp2Vx4Qm1",
33+
"zone_id": null,
34+
"vendor_id": null,
35+
"parent_fleet_id": null,
36+
"photo_id": null,
3637
"updated_at": "2026-05-07T08:30:00.000000Z",
3738
"created_at": "2026-05-07T08:30:00.000000Z"
3839
}

0 commit comments

Comments
 (0)