bimplus-websdk 1.0.63 → 1.0.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +64 -0
- package/.github/instructions/api-core.instructions.md +144 -0
- package/.github/instructions/project-attributes.instructions.md +172 -0
- package/.github/instructions/test-conventions.instructions.md +209 -0
- package/.github/instructions/typescript-declarations.instructions.md +116 -0
- package/dist/bimplus-websdk.js +1 -1
- package/eslint.config.mjs +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# GitHub Copilot Instructions — Bimplus WebSDK
|
|
2
|
+
|
|
3
|
+
> **Path convention:** This repository contains multiple packages. Unless stated otherwise, **all paths in this document are relative to the `websdk/` package root** (i.e. `websdk/` in the repository). For example, `src/Api/Api.js` refers to `websdk/src/Api/Api.js` in the repository, and `types/bimplus-websdk.d.ts` refers to `websdk/types/bimplus-websdk.d.ts`.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
This is the **Bimplus WebSDK** (`bimplus-websdk`), a JavaScript wrapper around the Bimplus REST API developed by Allplan. It is a UMD library built with Webpack + Babel, targeting both browser and Node/CommonJS consumers. The entry point is `websdk/src/Api/Api.js` and the compiled output is `websdk/dist/bimplus-websdk.js`.
|
|
7
|
+
|
|
8
|
+
## Repository Structure
|
|
9
|
+
All paths below are relative to `websdk/`:
|
|
10
|
+
- `src/Api/` — All API module source files (one class per resource, e.g. `Projects.js`, `Models.js`)
|
|
11
|
+
- `test/api_tests/` — QUnit integration tests (one file per API module, suffixed `Tests.js`)
|
|
12
|
+
- `test/helpers/` — Shared test helpers (`ApiHelper.js`, etc.)
|
|
13
|
+
- `types/bimplus-websdk.d.ts` — TypeScript type declarations for the public API
|
|
14
|
+
- `dist/` — Build output (do not edit manually)
|
|
15
|
+
- `documentation/` — Auto-generated docs (JSDoc via `documentation` package)
|
|
16
|
+
|
|
17
|
+
## Language & Style
|
|
18
|
+
- **ES2022 modules** (`import`/`export`) throughout `src/` and `test/`
|
|
19
|
+
- **No TypeScript** in source files — only `.js`. The `types/` folder contains handwritten `.d.ts` declarations that must be kept in sync manually when adding new public methods.
|
|
20
|
+
- **Semicolons are required** (enforced by ESLint: `semi: ["warn", "always"]`).
|
|
21
|
+
- Use `const`/`let` — never `var`.
|
|
22
|
+
- Module pattern: each API resource is a constructor function (not a class), attached to the `Api` class instance via `this.<namespace> = new ResourceName(this)`.
|
|
23
|
+
- All async API calls return a **jQuery `$.ajax` promise** (via `AjaxRequest` / `RequestUtils.js`). Do not use `fetch` or `axios` in `src/`.
|
|
24
|
+
- Keep JSDoc comments on every public method following the existing `@namespace` / `@memberof` / `@param` / `@return` pattern so `documentation build` continues to work.
|
|
25
|
+
|
|
26
|
+
## Adding a New API Module
|
|
27
|
+
1. Create `src/Api/MyResource.js` following the existing constructor-function pattern.
|
|
28
|
+
2. Import and instantiate it in `src/Api/Api.js` (follow the existing import list and `this.myResource = new MyResource(this)` in the constructor).
|
|
29
|
+
3. Add corresponding tests in `test/api_tests/MyResourceTests.js` using QUnit + `ApiHelper`.
|
|
30
|
+
4. Add TypeScript declarations in `types/bimplus-websdk.d.ts`.
|
|
31
|
+
5. Document with JSDoc `@namespace`/`@memberof api` tags.
|
|
32
|
+
|
|
33
|
+
## Testing
|
|
34
|
+
- Framework: **QUnit** running inside **Karma** (browser-based).
|
|
35
|
+
- Tests are integration tests that call the real Bimplus dev API — they require `BIM_PASS` env variable.
|
|
36
|
+
- Run all tests: `npm test`
|
|
37
|
+
- Run headless Chrome: `npm run test-chrome-headless`
|
|
38
|
+
- Test helpers live in `test/helpers/ApiHelper.js`. Use `ApiHelper` to set up team/project context before assertions.
|
|
39
|
+
- Assert HTTP error responses with `assertHttpStatusText(assert, e, "Bad Request")` (global helper injected by Karma).
|
|
40
|
+
|
|
41
|
+
## Build
|
|
42
|
+
- Dev build (with source maps): `npm run build`
|
|
43
|
+
- Production build (minified): `npm run build-prod`
|
|
44
|
+
- Lint: `npm run eslint`
|
|
45
|
+
- Output format: UMD, entry `src/Api/Api.js` → `dist/bimplus-websdk.js`
|
|
46
|
+
- Webpack config split: `webpack.common.js` (shared), `webpack.dev.js`, `webpack.prod.js`
|
|
47
|
+
|
|
48
|
+
## Documentation
|
|
49
|
+
- Generate HTML docs: `npm run build-doc`
|
|
50
|
+
- Generate Markdown: `npm run build-docMd`
|
|
51
|
+
- Every public method in `src/Api/*.js` must pass `documentation lint` (`npm run lint-doc`) — keep JSDoc valid.
|
|
52
|
+
|
|
53
|
+
## Key Conventions
|
|
54
|
+
- URL helpers: use `api.getApiTeamUrl()` for team-scoped endpoints, `api.getApiUrl()` for global endpoints.
|
|
55
|
+
- Query parameters are passed as `{ queryParams: { key: value } }` in the `params` argument to `api.request.get()`.
|
|
56
|
+
- Chunked upload/download helpers (`getChunked`, `putChunked`) are in `AjaxRequest.js` — use them for large payloads.
|
|
57
|
+
- IDB caching is opt-in via `IDBCache.js` / `CachedRequest.js`; do not add caching logic directly to resource modules.
|
|
58
|
+
- Global flags `window.BIMPLUS_SDK_ENABLE_CACHE`, `window.BIMPLUS_SDK_LOG_API_CALLS`, `window.BIMPLUS_SDK_DISABLE_ETAG`, `window.BIMPLUS_SDK_PENDING_REQUESTS` are intentional public hooks — do not remove them.
|
|
59
|
+
|
|
60
|
+
## What to Avoid
|
|
61
|
+
- Do not introduce new runtime dependencies without discussing it first; the current dependency footprint (`async`, `dexie`, `jquery`) is intentionally minimal.
|
|
62
|
+
- Do not convert constructor functions to ES6 `class` syntax — this would break backward compatibility with consumers using the UMD bundle.
|
|
63
|
+
- Do not add `async/await` to `src/` code without ensuring Babel transpilation handles it (check `@babel/plugin-transform-runtime` config).
|
|
64
|
+
- Do not commit credentials, tokens, or real GUIDs from the test environment into source files.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "websdk/src/Api/Api.js, websdk/src/Api/AjaxRequest.js, websdk/src/Api/RequestUtils.js"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Bimplus WebSDK — Core API & Request Layer
|
|
6
|
+
|
|
7
|
+
## Api Class Initialization
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { Api, createDefaultConfig } from './src/Api/Api';
|
|
11
|
+
|
|
12
|
+
const config = createDefaultConfig('dev'); // 'dev' | 'stage' | 'prod' (default)
|
|
13
|
+
const api = new Api(config);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### `createDefaultConfig(env?)` Environments
|
|
17
|
+
|
|
18
|
+
| `env` value | Host |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `'dev'` | `api-dev.bimplus.net` |
|
|
21
|
+
| `'stage'` | `api-stage.bimplus.net` |
|
|
22
|
+
| `'prod'` / omitted | `api.bimplus.net` |
|
|
23
|
+
|
|
24
|
+
### Config Shape
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"protocol": "https://",
|
|
29
|
+
"host": "api-dev.bimplus.net",
|
|
30
|
+
"version": "v2",
|
|
31
|
+
"cache": false
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `version` defaults to `'v2'` if omitted from a custom config object.
|
|
36
|
+
- `cache: true` enables IDB-backed caching via `CachedRequest`. If IndexedDB is not supported by the browser, it falls back silently to `AjaxRequest` with a console warning.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Authentication & Setup Flow
|
|
41
|
+
|
|
42
|
+
After constructing `Api`, always perform these three steps before calling any resource API:
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
// 1. Obtain a token
|
|
46
|
+
const auth = await api.authorize.post(username, password, applicationId);
|
|
47
|
+
|
|
48
|
+
// 2. Set the token (token type 'BimPlus' is the standard)
|
|
49
|
+
api.setAccessTokenAndType(auth.access_token, 'BimPlus');
|
|
50
|
+
|
|
51
|
+
// 3. Set the team context (required for all team-scoped endpoints)
|
|
52
|
+
api.setTeamSlug('my-team-slug');
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
| Method | Purpose |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `api.setAccessToken(token)` | Sets token only |
|
|
58
|
+
| `api.setTokenType(type)` | Sets token type only |
|
|
59
|
+
| `api.setAccessTokenAndType(token, type)` | Sets both at once (preferred) |
|
|
60
|
+
| `api.setTeamSlug(slug)` | Required before any team-scoped request |
|
|
61
|
+
| `api.getTeamSlug()` | Returns current team slug |
|
|
62
|
+
| `api.getAccessToken()` | Returns current token |
|
|
63
|
+
| `api.getAccessTokenAndType()` | Returns `{ token, tokenType }` |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## URL Building
|
|
68
|
+
|
|
69
|
+
| Method | Example output |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `api.getApiUrl()` | `https://api-dev.bimplus.net/v2/` |
|
|
72
|
+
| `api.getApiTeamUrl()` | `https://api-dev.bimplus.net/v2/my-team-slug/` |
|
|
73
|
+
|
|
74
|
+
- `getApiTeamUrl()` logs a `console.error` if `teamSlug` is not set — it does **not** throw.
|
|
75
|
+
- Use `getApiUrl()` for global (non-team-scoped) endpoints: `authorize`, `auth-forgot`, `cross-token`, `validate`, `administration/backups`, `.well-known/openid-configuration`.
|
|
76
|
+
- Use `getApiTeamUrl()` for all other endpoints (projects, objects, divisions, issues, etc.).
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Request Layer (`AjaxRequest`)
|
|
81
|
+
|
|
82
|
+
All resource modules call `api.request.get / put / post / del`. The signatures are:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
api.request.get(url, ajaxParams?)
|
|
86
|
+
api.request.put(url, data, ajaxParams?)
|
|
87
|
+
api.request.post(url, data, ajaxParams?)
|
|
88
|
+
api.request.del(url, data?, ajaxParams?)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `ajaxParams` shape
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
{
|
|
95
|
+
queryParams: { key: 'value' }, // appended as URL query string
|
|
96
|
+
contentType: 'application/json; charset=utf-8', // default
|
|
97
|
+
dataType: 'json', // default; set to null for non-JSON responses
|
|
98
|
+
processData: false, // default
|
|
99
|
+
eTag: '<etag-value>', // adds If-None-Match header (unless BIMPLUS_SDK_DISABLE_ETAG is set)
|
|
100
|
+
headers: { 'X-Custom': 'val' }, // extra request headers
|
|
101
|
+
useXMLHttpRequest: true, // use XMLHttpRequest instead of $.ajax (e.g. binary/streaming)
|
|
102
|
+
responseType: 'arraybuffer', // XHR binary response type
|
|
103
|
+
xhrUploadProgress: function(e) {}, // upload progress callback for XHR
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Chunked helpers
|
|
108
|
+
|
|
109
|
+
Use these for large payloads — never implement custom chunking inside resource modules:
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
api.request.getChunked(url, ajaxConfig) // chunked GET
|
|
113
|
+
api.request.putChunked(url, data, ajaxConfig) // chunked PUT
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Global Window Flags
|
|
119
|
+
|
|
120
|
+
All flags are optional. They must be set **before** any request is made.
|
|
121
|
+
|
|
122
|
+
| Flag | Type | Effect |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `window.BIMPLUS_SDK_ENABLE_CACHE` | `boolean` | Enables jQuery `$.ajax` browser-level cache |
|
|
125
|
+
| `window.BIMPLUS_SDK_LOG_API_CALLS` | `boolean` | Console-logs every request URL + parsed response |
|
|
126
|
+
| `window.BIMPLUS_SDK_DISABLE_ETAG` | `boolean` | Suppresses `If-None-Match` header even when `eTag` is set in ajaxParams |
|
|
127
|
+
| `window.BIMPLUS_SDK_PENDING_REQUESTS` | `number` | Incremented on request start, decremented on complete — useful to track in-flight requests |
|
|
128
|
+
| `window.BIMPLUS_SDK_ENABLE_DEBUG_LOGS` | `boolean` | Logs token, tokenType, teamSlug, and config to console during `Api` initialization |
|
|
129
|
+
|
|
130
|
+
> `BIMPLUS_SDK_ENABLE_DEBUG_LOGS` is intentional — do not remove it.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Models vs. Divisions Naming
|
|
135
|
+
|
|
136
|
+
The Bimplus REST API uses the term **"divisions"** for what the SDK exposes as **`api.models`**. This is a legacy naming mismatch:
|
|
137
|
+
|
|
138
|
+
| SDK namespace | REST endpoint |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `api.models.get(modelId)` | `GET .../divisions/:id` |
|
|
141
|
+
| `api.models.getRevisions(divisionId)` | `GET .../divisions/:id/revisions` |
|
|
142
|
+
| `api.models.getDisciplines(divisionId)` | `GET .../divisions/:id/disciplines` |
|
|
143
|
+
|
|
144
|
+
Always use `api.models` in SDK code. Use `divisionId` as the parameter name for the ID (not `modelId`) when the underlying endpoint says `divisions`.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "websdk/src/Api/Projects.js, websdk/test/api_tests/Project*.js, websdk/types/bimplus-websdk.d.ts"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Bimplus Project Attributes
|
|
6
|
+
|
|
7
|
+
## Core Project Object (`projects.get()`)
|
|
8
|
+
|
|
9
|
+
When retrieving a project via `api.projects.get(id?)`, the returned JSON object contains these fields:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"id": "<guid>",
|
|
14
|
+
"name": "My Project",
|
|
15
|
+
"shortDescr": "Short project description",
|
|
16
|
+
"address": "Street, City, Country",
|
|
17
|
+
"teamSlug": "my-team-slug",
|
|
18
|
+
"teamName": "My Team",
|
|
19
|
+
"rights": { ... },
|
|
20
|
+
"disciplines": [ ... ]
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
| Field | Type | Notes |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `id` | `string (Guid)` | Unique project identifier |
|
|
27
|
+
| `name` | `string` | Display name of the project |
|
|
28
|
+
| `shortDescr` | `string` | Short description / subtitle |
|
|
29
|
+
| `address` | `string` | Physical address of the project |
|
|
30
|
+
| `teamSlug` | `string` | URL slug of the owning team |
|
|
31
|
+
| `teamName` | `string` | Display name of the owning team |
|
|
32
|
+
| `rights` | `object` | Current user's access rights on this project |
|
|
33
|
+
| `disciplines` | `array` | List of model disciplines; omitted when `noDisciplines=true` |
|
|
34
|
+
|
|
35
|
+
> `disciplines` can be suppressed by calling `api.projects.get(id, true)` for performance on large project lists.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Extended Attribute Object (`objects.getAttributes(projectId, "", projectId, "")`)
|
|
40
|
+
|
|
41
|
+
The full project attribute set — as shown on the BimPortal project detail page — is retrieved via:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const projectAttributes = await api.objects.getAttributes(projectId, "", projectId, "");
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Top-level shape
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"id": "<project guid>",
|
|
52
|
+
"name": "My Project",
|
|
53
|
+
"type": "Project",
|
|
54
|
+
"elementtyp": "8d27ae6d-3c9a-4201-8a4d-bf0225861788",
|
|
55
|
+
"attributes": { ... },
|
|
56
|
+
"localizedAttributeGroups": { ... }
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| Field | Value / Notes |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `id` | Project GUID |
|
|
63
|
+
| `name` | Project name |
|
|
64
|
+
| `type` | Always `"Project"` |
|
|
65
|
+
| `elementtyp` | Fixed GUID `8d27ae6d-3c9a-4201-8a4d-bf0225861788` — identifies the project element type in Bimplus |
|
|
66
|
+
| `attributes` | Map of attribute groups (see below) |
|
|
67
|
+
| `localizedAttributeGroups` | Localized display names for each group key |
|
|
68
|
+
|
|
69
|
+
### Attribute groups (`attributes`)
|
|
70
|
+
|
|
71
|
+
The `attributes` object always contains exactly **7 groups**:
|
|
72
|
+
|
|
73
|
+
| Group key | Description |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `address` | Physical address fields of the project |
|
|
76
|
+
| `others` | Miscellaneous project settings (see details below) |
|
|
77
|
+
| `project` | Core project metadata |
|
|
78
|
+
| `project contact` | Contact person information |
|
|
79
|
+
| `project meeting` | Project meeting data |
|
|
80
|
+
| `project offset` | Internal coordinate system offset (x, y, z) |
|
|
81
|
+
| `project statistics` | Computed statistics and counters |
|
|
82
|
+
|
|
83
|
+
### Individual attribute shape
|
|
84
|
+
|
|
85
|
+
Each entry inside a group is keyed by a localized attribute name and has the following structure:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"id": "<guid>",
|
|
90
|
+
"name": "Offset X",
|
|
91
|
+
"description": "Offset X",
|
|
92
|
+
"group": "project offset",
|
|
93
|
+
"type": "double",
|
|
94
|
+
"unit": "m",
|
|
95
|
+
"value": 100
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Field | Notes |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `id` | Stable GUID — use this for lookups, not the name (names can be localized) |
|
|
102
|
+
| `name` | Human-readable attribute name |
|
|
103
|
+
| `description` | Longer description of the attribute |
|
|
104
|
+
| `group` | The group this attribute belongs to |
|
|
105
|
+
| `type` | Data type: `"string"`, `"double"`, `"boolean"`, `"guid"`, etc. |
|
|
106
|
+
| `unit` | Unit string, e.g. `"m"` — may be absent for non-numeric types |
|
|
107
|
+
| `value` | The current value |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Well-known Attribute GUIDs
|
|
112
|
+
|
|
113
|
+
Use `id` for reliable lookups — never match by `name` alone (names are localizable).
|
|
114
|
+
|
|
115
|
+
### `project offset` group — internal offsets
|
|
116
|
+
|
|
117
|
+
| Attribute | GUID | Type | Unit |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| Offset X | `e276009f-f637-43ef-90ee-8e145e8dc41c` | `double` | `m` |
|
|
120
|
+
| Offset Y | `2e112c8a-47fb-40ba-b7b3-3f601daa127c` | `double` | `m` |
|
|
121
|
+
| Offset Z | `d0cbf9eb-25b2-4443-b23d-8c67396822e8` | `double` | `m` |
|
|
122
|
+
|
|
123
|
+
### `others` group — user-defined global offsets and settings
|
|
124
|
+
|
|
125
|
+
| Attribute | GUID | Type | Unit |
|
|
126
|
+
|---|---|---|---|
|
|
127
|
+
| Δ x (global coordinate x) | `ecea05de-9450-48f8-a8f2-f1de32b7cd5c` | `double` | `m` |
|
|
128
|
+
| Δ y (global coordinate y) | `6d0e5735-2c05-4b24-90c2-663ba234a6f8` | `double` | `m` |
|
|
129
|
+
| Δ z (global coordinate z) | `43b6bb07-0b1f-49ff-b226-33befba937e2` | `double` | `m` |
|
|
130
|
+
| Default language for BCF export | `2ae6edb6-bfbd-4ce6-ae7e-7dcd7b0abc2e` | `string` | — |
|
|
131
|
+
| Project currency | `513d2bdd-f994-46b6-a629-0a5e694893a5` | `string` | — |
|
|
132
|
+
| Properties template | `3d7404a8-f2e7-4418-b87a-daf1597cfb49` | `guid` | — |
|
|
133
|
+
| RestrictMemberAccessInPortal | `f4437afc-c55b-4227-9d97-89035ce8dafd` | `boolean` | — |
|
|
134
|
+
| Units (Units and Dimensions) | `a907b2b1-91f2-46e6-a045-7fee485c458b` | `guid` | — |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Related Sub-resources (via `api.projects.*`)
|
|
139
|
+
|
|
140
|
+
The following child resources are accessible directly from a project ID:
|
|
141
|
+
|
|
142
|
+
| Method | Endpoint pattern | Returns |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| `getModels(projectId)` | `.../projects/:id/divisions` | Array of model/discipline objects |
|
|
145
|
+
| `getMembers(projectId)` | `.../projects/:id/members` | Array of `{ member, group }` objects |
|
|
146
|
+
| `getPins(projectId)` | `.../projects/:id/pins` | Array of pin objects |
|
|
147
|
+
| `getSpots(projectId)` | `.../projects/:id/pins` | Alias for pins |
|
|
148
|
+
| `getAttachments(projectId, revision?, queryParams?)` | `.../projects/:id/attachments` | Array of attachment objects |
|
|
149
|
+
| `getTopology(projectId)` | `.../projects/:id/topology` | Topology tree with `children` |
|
|
150
|
+
| `getComments(projectId)` | `.../projects/:id/comments` | Array of comment objects |
|
|
151
|
+
| `getHyperlinks(projectId)` | `.../projects/:id/hyperlinks` | Array of hyperlink objects |
|
|
152
|
+
| `getIssues(projectId, filterScene?, thumbnailUrl?)` | `.../projects/:id/issues` | Array of issue/task objects |
|
|
153
|
+
| `getIssuesShortInfo(projectId)` | `.../projects/:id/issues?shortinfo=true` | Array of summarized issue info |
|
|
154
|
+
| `getSlideshows(projectId)` | `.../projects/:id/slideshows` | Array of slideshow objects |
|
|
155
|
+
| `getThumbnail(thumbnailId)` | `.../thumbnail/:id` | Thumbnail data |
|
|
156
|
+
| `getProjectInfo(projectId)` | `.../projects/:id` | Same shape as `projects.get(id)` |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Writable Project Fields (POST / PUT)
|
|
161
|
+
|
|
162
|
+
When creating (`projects.post(data)`) or updating (`projects.put(id, data)`) a project, pass a JSON-stringified object with:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"name": "My Awesome Building",
|
|
167
|
+
"shortDescr": "Example description",
|
|
168
|
+
"address": "Street, City"
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The response returns the full project object including the server-assigned `id` and `teamSlug`.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "websdk/test/api_tests/**, websdk/test/helpers/**"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Bimplus WebSDK — Test Conventions
|
|
6
|
+
|
|
7
|
+
## Test Framework
|
|
8
|
+
|
|
9
|
+
- **QUnit** + **Karma** (browser-based integration tests against the real Bimplus API)
|
|
10
|
+
- Tests are **not** unit tests — they require a live API connection and valid credentials
|
|
11
|
+
- One test file per API module, named `<Module>Tests.js` in `test/api_tests/`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ApiHelper Setup
|
|
16
|
+
|
|
17
|
+
Import `ApiHelper` at the top of every test file:
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import { ApiHelper } from '../helpers/ApiHelper';
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Instantiate it inside each `QUnit.test` callback (not at module level):
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
const helper = new ApiHelper();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Team and Project Context
|
|
30
|
+
|
|
31
|
+
Always set context before calling resource APIs:
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
await helper.setTeamByName(); // uses helper.defaultTeam
|
|
35
|
+
await helper.setTeamByName('My Team'); // specific team by display name
|
|
36
|
+
|
|
37
|
+
const projectId = await helper.setProjectByName(); // uses helper.BasicProject.name
|
|
38
|
+
const projectId = await helper.setProjectByName(null, 'Proj'); // specific project name
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `setTeamByName` performs `login()` automatically, looks up the team by name, and calls `api.setTeamSlug()`.
|
|
42
|
+
- `setProjectByName` also calls `setTeamByName` internally — no need to call both.
|
|
43
|
+
- Both return the resolved `id`.
|
|
44
|
+
|
|
45
|
+
### Accessing the API Instance
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
helper.api.projects.get(projectId);
|
|
49
|
+
helper.api.models.get(modelId);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Environment Variables
|
|
55
|
+
|
|
56
|
+
| Variable | Environment | Purpose |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `BIM_PASS` | `dev` | Password for `bimplus.web.sdk@allplan.com` on dev |
|
|
59
|
+
| `BIM_PASS_STAGE` | `stage` | Password for the same user on stage |
|
|
60
|
+
| `BIM_PASS_PROD` | `prod` | Password for the same user on prod |
|
|
61
|
+
|
|
62
|
+
The environment is auto-detected by `ApiHelper` from the Karma `host` config. Never hard-code passwords.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Test Fixture Constants
|
|
67
|
+
|
|
68
|
+
All GUIDs for test data are exposed on the `ApiHelper` instance. Always reference these — never hard-code GUIDs in test files.
|
|
69
|
+
|
|
70
|
+
### Shared fixtures (environment-independent)
|
|
71
|
+
|
|
72
|
+
| Property | Description |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `helper.BasicProject` | Main project for most object/topology/filter tests |
|
|
75
|
+
| `helper.BasicProject.projectId` | Project GUID |
|
|
76
|
+
| `helper.BasicProject.divisionId_m1` | Division (model) GUID for model `m1` |
|
|
77
|
+
| `helper.BasicProject.wallId_m1` | Object GUID for a wall in model `m1` |
|
|
78
|
+
| `helper.BasicProject.doorId_m1` | Object GUID for a door in model `m1` |
|
|
79
|
+
| `helper.BasicProjectTest` | Project used for create/update/delete project tests |
|
|
80
|
+
| `helper.BasicProjectWithOffsets` | Project with known coordinate offsets |
|
|
81
|
+
| `helper.BasicStructuresTest` | Project for structure tests |
|
|
82
|
+
| `helper.BasicMarkupTests` | Project for annotation/markup tests |
|
|
83
|
+
| `helper.BasicSlideshowTests` | Project for slideshow tests |
|
|
84
|
+
| `helper.undefinedId` | `"00000000-0000-0000-0000-000000000000"` — use for "not found" tests |
|
|
85
|
+
| `helper.dummyId` | `"AAAAAAAA-0000-AAAA-0000-000000000000"` — use for invalid-ID tests |
|
|
86
|
+
|
|
87
|
+
### Environment-specific fixtures (differ between dev / stage / prod)
|
|
88
|
+
|
|
89
|
+
| Property | Description |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `helper.BasicModelTest` | Project with 3 revisions (rev1Id, rev2Id, rev3Id) |
|
|
92
|
+
| `helper.BasicImportTest` | Project + model for IFC import tests |
|
|
93
|
+
| `helper.BasicOverlayTests` | Project with overlays and versioned attachments |
|
|
94
|
+
| `helper.BasicConnexisTest` | Project for structural Connexis tests |
|
|
95
|
+
| `helper.BasicWorkflowTest` | Project for workflow template tests |
|
|
96
|
+
|
|
97
|
+
### User constants
|
|
98
|
+
|
|
99
|
+
| Property | Value |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `helper.defaultEMail` | `bimplus.web.sdk@allplan.com` |
|
|
102
|
+
| `helper.defaultTeam` | Team display name for the current environment |
|
|
103
|
+
| `helper.defaultTeamSlug` | URL slug of the test team |
|
|
104
|
+
| `helper.defaultTeamId` | GUID of the test team |
|
|
105
|
+
| `helper.defaultAppId` | `5F43560D-9B0C-4F3C-85CB-B5721D098F7B` (shared across envs) |
|
|
106
|
+
| `helper.invitationUserEmail` | `bimplus.renderer.test@allplan.com` — for member/message tests |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## QUnit Test Pattern
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
QUnit.module("api_tests/MyResourceTests", function () {
|
|
114
|
+
|
|
115
|
+
QUnit.test("Get resource", async function (assert) {
|
|
116
|
+
assert.expect(2); // always declare expected assertion count up front
|
|
117
|
+
|
|
118
|
+
const helper = new ApiHelper();
|
|
119
|
+
await helper.setTeamByName();
|
|
120
|
+
|
|
121
|
+
const result = await helper.api.myResource.get();
|
|
122
|
+
assert.ok(result.length > 0);
|
|
123
|
+
assert.ok(result[0].name);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
QUnit.test("Handle HTTP error", async function (assert) {
|
|
127
|
+
assert.expect(2);
|
|
128
|
+
|
|
129
|
+
const helper = new ApiHelper();
|
|
130
|
+
await helper.setTeamByName();
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
await helper.api.myResource.get(helper.undefinedId);
|
|
134
|
+
assert.ok(false, 'Should have thrown');
|
|
135
|
+
} catch (e) {
|
|
136
|
+
assertHttpStatusText(assert, e, "Not Found"); // global injected by Karma — do NOT import it
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Key rules:
|
|
144
|
+
- **Always** call `assert.expect(N)` at the start of each test
|
|
145
|
+
- Use `async function` + `await` — no `.then()` chains or callbacks in test body
|
|
146
|
+
- Module name must match the file path exactly: `"api_tests/MyResourceTests"`
|
|
147
|
+
- `assertHttpStatusText` is a **Karma-injected global** — never import it
|
|
148
|
+
- Use `helper.undefinedId` or `helper.dummyId` to trigger expected HTTP errors, not random strings
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Specialist Test Helpers
|
|
153
|
+
|
|
154
|
+
For complex resource domains, additional helpers exist in `test/helpers/`:
|
|
155
|
+
|
|
156
|
+
| Helper | Used for |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `AnnotationsHelper` | Creating annotation payloads |
|
|
159
|
+
| `LabelsHelper` | Creating label payloads with object IDs |
|
|
160
|
+
| `DimensionsHelper` | Creating dimension payloads with object IDs |
|
|
161
|
+
| `MultipartFormDataHelper` | Building multipart/form-data for file uploads |
|
|
162
|
+
| `MessagesHelper` | Cleaning up all messages for a user before tests run |
|
|
163
|
+
|
|
164
|
+
Import them alongside `ApiHelper` when needed:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
import { LabelsHelper } from '../helpers/LabelsHelper';
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Message Cleanup in `beforeEach`
|
|
173
|
+
|
|
174
|
+
Some backend services (notably `ExportWorkerService`) deliver notification messages to `defaultUserId` **asynchronously** after an operation completes (e.g. an IFC export triggered by `ExportServiceTests`). Because test files run alphabetically and `ExportServiceTests` (E) runs well before `InvitationsTests` (I) and `MessagesTests` (M), these delayed notifications can arrive in the user's inbox during a later test, causing false failures on empty-inbox assertions.
|
|
175
|
+
|
|
176
|
+
**Any test module that asserts on message counts must call `deleteAllMessages` in its `beforeEach`:**
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
import { ApiHelper } from '../helpers/ApiHelper';
|
|
180
|
+
import { deleteAllMessages } from '../helpers/MessagesHelper';
|
|
181
|
+
|
|
182
|
+
QUnit.module("api_tests/MyTests", function (hooks) {
|
|
183
|
+
|
|
184
|
+
hooks.beforeEach(async function () {
|
|
185
|
+
const helper = new ApiHelper();
|
|
186
|
+
await helper.setProjectByName();
|
|
187
|
+
await deleteAllMessages(helper.api, helper.defaultUserId);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`deleteAllMessages(api, userId)` fetches all four message endpoints in parallel — `getUserMessages` (inbox), `getSentUserMessages` (sent), `getSentGroupMessages` (group-sent), and `getUserDashboardMessages` (dashboard) — deduplicates IDs across all four, and deletes everything found in parallel. It exits only after **two consecutive empty checks**, confirming a stable empty state. This prevents the race where a background-service message arrives in the window between an empty fetch and the next test assertion. A 1-second pause separates each pass (up to 5 attempts total).
|
|
194
|
+
|
|
195
|
+
> **Do not** replicate this loop inline. Always use `deleteAllMessages` from `MessagesHelper`.
|
|
196
|
+
|
|
197
|
+
### When to find messages by ID, not by index
|
|
198
|
+
|
|
199
|
+
When verifying that a newly created message appears in a list alongside possible system messages, always locate it by its known ID rather than assuming array position:
|
|
200
|
+
|
|
201
|
+
```js
|
|
202
|
+
// Wrong — breaks when system messages sort before the test message
|
|
203
|
+
assert.equal(messages[0].topic, "MY_TOPIC");
|
|
204
|
+
|
|
205
|
+
// Correct
|
|
206
|
+
const testMessage = messages.find((m) => m.id === createdMessage.id);
|
|
207
|
+
assert.ok(testMessage, "test message found in sent messages");
|
|
208
|
+
assert.equal(testMessage.topic, "MY_TOPIC");
|
|
209
|
+
```
|