@hamak/smart-data-dico 1.0.0
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/README.md +292 -0
- package/backend/package.json +51 -0
- package/backend/src/__tests__/integration/api.test.ts +149 -0
- package/backend/src/__tests__/setup.ts +24 -0
- package/backend/src/__tests__/utils/testUtils.ts +76 -0
- package/backend/src/adapters/EntityFileAdapter.ts +154 -0
- package/backend/src/adapters/YamlFileInfoEnricher.ts +52 -0
- package/backend/src/controllers/authController.ts +131 -0
- package/backend/src/controllers/diagramController.ts +143 -0
- package/backend/src/controllers/dictionaryController.ts +306 -0
- package/backend/src/controllers/importExportController.ts +64 -0
- package/backend/src/controllers/perspectiveController.ts +90 -0
- package/backend/src/controllers/serviceController.ts +418 -0
- package/backend/src/controllers/stereotypeController.ts +59 -0
- package/backend/src/controllers/versionController.ts +226 -0
- package/backend/src/kernel/config.ts +43 -0
- package/backend/src/middleware/auth.ts +128 -0
- package/backend/src/middleware/jwtAuth.ts +100 -0
- package/backend/src/models/Dictionary.ts +38 -0
- package/backend/src/models/EntitySchema.ts +393 -0
- package/backend/src/models/__tests__/Dictionary.test.ts +92 -0
- package/backend/src/models/__tests__/EntitySchema.test.ts +119 -0
- package/backend/src/routes/index.ts +120 -0
- package/backend/src/scripts/migrate-to-uuid.ts +24 -0
- package/backend/src/server.ts +140 -0
- package/backend/src/services/__mocks__/entityService.ts +38 -0
- package/backend/src/services/__mocks__/serviceService.ts +88 -0
- package/backend/src/services/__mocks__/versionService.ts +38 -0
- package/backend/src/services/__tests__/dictionaryService.test.ts +74 -0
- package/backend/src/services/diagramService.ts +165 -0
- package/backend/src/services/dictionaryService.ts +582 -0
- package/backend/src/services/entityService.ts +102 -0
- package/backend/src/services/exportService.ts +172 -0
- package/backend/src/services/importService.ts +208 -0
- package/backend/src/services/perspectiveService.ts +276 -0
- package/backend/src/services/qualityService.ts +121 -0
- package/backend/src/services/serviceService.ts +763 -0
- package/backend/src/services/stereotypeService.ts +98 -0
- package/backend/src/services/versionService.ts +135 -0
- package/backend/src/setupTests.ts +12 -0
- package/backend/src/utils/__mocks__/fileOperations.ts +116 -0
- package/backend/src/utils/fileOperations.ts +602 -0
- package/backend/src/utils/logger.ts +38 -0
- package/backend/src/utils/migration.ts +254 -0
- package/backend/src/utils/swagger.ts +358 -0
- package/backend/src/utils/uuid.ts +41 -0
- package/backend/tsconfig.json +20 -0
- package/bin/cli.js +129 -0
- package/data-dictionaries/stereotypes.yaml +91 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# Smart Data Dictionary
|
|
2
|
+
|
|
3
|
+
A collaborative data dictionary management system for modeling, documenting, and governing your organization's data landscape. Built on a microkernel plugin architecture for modularity, with file-based persistence (YAML/Git) designed for both technical and non-technical users.
|
|
4
|
+
|
|
5
|
+
## Core Concepts
|
|
6
|
+
|
|
7
|
+
### Packages
|
|
8
|
+
|
|
9
|
+
Packages organize entities into logical groups reflecting your system architecture (microservices, modules, bounded contexts). They are hierarchical -- packages can contain sub-packages, forming a tree that mirrors your application structure.
|
|
10
|
+
|
|
11
|
+
- Navigable via nested URLs: `/packages/eShop/ordering/entities/Order`
|
|
12
|
+
- Each package has a dashboard with entity counts, relationship counts, and metadata
|
|
13
|
+
- Package names follow kebab-case convention
|
|
14
|
+
|
|
15
|
+
### Entities & Attributes
|
|
16
|
+
|
|
17
|
+
Entities are the primary modeling unit -- they represent data structures (tables, documents, events, value objects). Each entity has:
|
|
18
|
+
|
|
19
|
+
- **Attributes** with types (string, number, integer, boolean, datetime, enum, object, array), constraints (min/max length, pattern, precision), and metadata
|
|
20
|
+
- **Nested attributes** for object and array types (e.g. `shippingAddress.postalCode`)
|
|
21
|
+
- **Status lifecycle**: draft -> submitted -> approved (or returned)
|
|
22
|
+
- **UUID-based identity** for stable cross-references
|
|
23
|
+
|
|
24
|
+
### Relationships
|
|
25
|
+
|
|
26
|
+
Relationships connect entities across packages. They are stored at the package level and define:
|
|
27
|
+
|
|
28
|
+
- **Source and target** entity (by UUID) with cardinality (one/many)
|
|
29
|
+
- **Navigation property names** for path traversal
|
|
30
|
+
- **Metadata** on the relationship itself
|
|
31
|
+
|
|
32
|
+
### Stereotypes
|
|
33
|
+
|
|
34
|
+
Stereotypes define metadata schemas that apply to specific element types (entity, attribute, or package). When assigned, they auto-populate required and optional metadata fields, ensuring consistency.
|
|
35
|
+
|
|
36
|
+
**Predefined stereotypes:**
|
|
37
|
+
|
|
38
|
+
| Stereotype | Applies To | Required Fields |
|
|
39
|
+
|-----------|-----------|-----------------|
|
|
40
|
+
| Aggregate Root | Entity | bounded-context |
|
|
41
|
+
| Reference Data | Entity | cacheable (flag) |
|
|
42
|
+
| Domain Event | Entity | event-source |
|
|
43
|
+
| Value Object | Entity | immutable (flag) |
|
|
44
|
+
| PII | Attribute | pii-category |
|
|
45
|
+
| Indexed | Attribute | - |
|
|
46
|
+
| Deprecated | Attribute | deprecated-since |
|
|
47
|
+
|
|
48
|
+
**Metadata types**: string, number, boolean, date, **flag** (semantic boolean rendered as toggle), **rule** (text + severity: info/warning/error).
|
|
49
|
+
|
|
50
|
+
### Perspectives
|
|
51
|
+
|
|
52
|
+
A Perspective captures a business view over a subset of the data model scoped to a business process (e.g. "Ordering", "Billing"). It is the core differentiating concept of the application.
|
|
53
|
+
|
|
54
|
+
**How it works:**
|
|
55
|
+
|
|
56
|
+
1. Select **root entities** -- the starting points of the business process
|
|
57
|
+
2. The system performs **BFS traversal** through relationships to discover all reachable entities
|
|
58
|
+
3. Each entity is identified by its **relationship path from root** (e.g. `Order/orderItems/product`)
|
|
59
|
+
4. The same entity reached via different paths is treated as separate usages
|
|
60
|
+
|
|
61
|
+
**Path grammar** -- uniform `/` navigation for all levels:
|
|
62
|
+
```
|
|
63
|
+
Order -- root entity
|
|
64
|
+
Order/totalAmount -- attribute on root
|
|
65
|
+
Order/orderItems -- relationship -> OrderItem
|
|
66
|
+
Order/orderItems/quantity -- attribute on OrderItem
|
|
67
|
+
Order/orderItems/product/sku -- deep chain -> attribute
|
|
68
|
+
Order/shippingAddress/postalCode -- attribute via relationship
|
|
69
|
+
Order/billingAddress/postalCode -- same attribute, different path
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**PerspectiveNode** annotations on any path:
|
|
73
|
+
- **`traverse: false`** -- frontier node (include but stop BFS here)
|
|
74
|
+
- **`exclude: true`** -- prune this path entirely
|
|
75
|
+
- **`metadata`** -- path-scoped annotations (not overrides -- new metadata specific to this perspective usage)
|
|
76
|
+
|
|
77
|
+
### Review Workflow
|
|
78
|
+
|
|
79
|
+
Entities go through a status lifecycle for team governance:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
draft --> submitted --> approved
|
|
83
|
+
\-> returned (with comments) --> submitted
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- **Comments** can target specific attributes (e.g. "Missing validation rule on discountRate")
|
|
87
|
+
- **Status transitions** are role-gated (editors submit, admins approve/return)
|
|
88
|
+
- Comments are stored as sidecar YAML files per entity
|
|
89
|
+
|
|
90
|
+
### Quality Metrics
|
|
91
|
+
|
|
92
|
+
The quality dashboard tracks documentation completeness:
|
|
93
|
+
|
|
94
|
+
- **Description coverage** -- % of entities/attributes with descriptions
|
|
95
|
+
- **Metadata compliance** -- % of entities with correct stereotype metadata
|
|
96
|
+
- **Relationship coverage** -- % of entities with at least one relationship
|
|
97
|
+
- **Per-entity scoring** -- weighted composite (30% description, 30% attribute descriptions, 20% relationships, 20% stereotype compliance)
|
|
98
|
+
- **Drill-down** from package level to individual entity gaps
|
|
99
|
+
|
|
100
|
+
## User Journeys
|
|
101
|
+
|
|
102
|
+
### Journey 1: Model a New Domain (Data Architect)
|
|
103
|
+
|
|
104
|
+
> *Alice documents the data model for an e-commerce platform.*
|
|
105
|
+
|
|
106
|
+
1. **Create packages** -- `eShop > Ordering`, `Catalog`, `Billing` via the package dashboard
|
|
107
|
+
2. **Define entities** -- `Order`, `OrderLine`, `Customer` with typed attributes and constraints
|
|
108
|
+
3. **Define relationships** -- `Order` to `OrderLine` (one-to-many) at the package level
|
|
109
|
+
4. **Assign stereotypes** -- Mark `Order` as `aggregate-root`, auto-populating `bounded-context` metadata
|
|
110
|
+
5. **Add metadata** -- Flag `Customer.email` as PII, add business rules
|
|
111
|
+
6. **Visualize** -- Open the Cytoscape.js interactive graph, verify the model, save the layout
|
|
112
|
+
7. **Save & publish** -- Save (commit to workspace), publish (push to shared)
|
|
113
|
+
|
|
114
|
+
### Journey 2: Define a Perspective (Business Analyst)
|
|
115
|
+
|
|
116
|
+
> *Bob captures the billing-specific view of the data model.*
|
|
117
|
+
|
|
118
|
+
1. **Create perspective** -- "Billing" with `Invoice` and `Payment` as root entities
|
|
119
|
+
2. **Review resolved paths** -- BFS walks relationships, discovers `Order`, `Customer`, `LineItem`
|
|
120
|
+
3. **Set frontiers** -- Mark `Product` as frontier (`traverse: false`) -- include but don't follow further
|
|
121
|
+
4. **Exclude paths** -- Exclude `Order/audit` (not relevant to billing)
|
|
122
|
+
5. **Add annotations** -- On `Order/billingAddress/postalCode`, add validation rule "Must match payment country"
|
|
123
|
+
6. **Visualize** -- Graph overlay highlights perspective members, dims others
|
|
124
|
+
|
|
125
|
+
### Journey 3: Review & Approve (Data Steward)
|
|
126
|
+
|
|
127
|
+
> *Carol reviews changes submitted by the team.*
|
|
128
|
+
|
|
129
|
+
1. **See pending entities** -- Order is in `submitted` status
|
|
130
|
+
2. **Add comments** -- "Missing description on id field" targeting specific attribute
|
|
131
|
+
3. **Return for revision** -- Status changes to `returned`, author gets notification
|
|
132
|
+
4. **Approve** -- After fixes, approve. Status moves to `approved`
|
|
133
|
+
5. **Check quality** -- Quality dashboard shows 90% for order-service, up from 80%
|
|
134
|
+
|
|
135
|
+
### Journey 4: Explore & Discover (Developer)
|
|
136
|
+
|
|
137
|
+
> *Dave needs to understand the data model for implementation.*
|
|
138
|
+
|
|
139
|
+
1. **Browse** -- Navigate `/packages/order-service` to see the dashboard with stats
|
|
140
|
+
2. **Search** -- Search "order" returns 27 results across entities, attributes, metadata, relationships, packages
|
|
141
|
+
3. **Filter** -- Faceted search: filter by type=relationship, service=order-service, stereotype=aggregate-root
|
|
142
|
+
4. **Impact analysis** -- Check "Impact" tab on Order: 2 relationships, 1 perspective reference
|
|
143
|
+
5. **Export** -- Export order-service as JSON Schema for code generation, or Markdown for documentation
|
|
144
|
+
|
|
145
|
+
### Journey 5: Version & Collaborate (Team Lead)
|
|
146
|
+
|
|
147
|
+
> *Eve manages versioning aligned with application releases.*
|
|
148
|
+
|
|
149
|
+
1. **Create workspace** -- Creates `workspace/billing-v2` (git branch with friendly name)
|
|
150
|
+
2. **Monitor status** -- Navbar shows "MAIN 8" (branch + unsaved count), dropdown shows ahead/behind
|
|
151
|
+
3. **Save & publish** -- Save page shows changed files, commit with message, push to remote
|
|
152
|
+
4. **Switch workspaces** -- Workspaces page lists branches, switch with one click
|
|
153
|
+
5. **Merge** -- Merge page selects source workspace, previews diff, merges
|
|
154
|
+
|
|
155
|
+
## Architecture
|
|
156
|
+
|
|
157
|
+
### Monorepo
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
smart-data-dico/
|
|
161
|
+
backend/ Express + TypeScript (controllers > services > models)
|
|
162
|
+
frontend/ React 18 + Vite + TypeScript + Tailwind CSS + DaisyUI
|
|
163
|
+
data-dictionaries/ YAML entity files, relationships, perspectives, stereotypes
|
|
164
|
+
Dockerfile Multi-stage production build
|
|
165
|
+
docker-compose.yml One-command deployment
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Microkernel Plugin Architecture
|
|
169
|
+
|
|
170
|
+
The frontend uses `@hamak/app-framework` with 11 plugins:
|
|
171
|
+
|
|
172
|
+
| Plugin | Responsibility |
|
|
173
|
+
|--------|---------------|
|
|
174
|
+
| **store** | Redux state management (10 domain slices) |
|
|
175
|
+
| **shell** | Layout, theming (synced with DaisyUI) |
|
|
176
|
+
| **auth** | JWT authentication with Auth0 |
|
|
177
|
+
| **data-dictionary** | Entity, package, relationship CRUD |
|
|
178
|
+
| **visualization** | Interactive Cytoscape.js graph |
|
|
179
|
+
| **search** | Full-text and faceted search |
|
|
180
|
+
| **version-control** | Save/publish, history |
|
|
181
|
+
| **perspective** | Perspective management and resolution |
|
|
182
|
+
| **remote-fs** | File operations via `@hamak/filesystem-server-impl` |
|
|
183
|
+
| **remote-git** | Git operations via `@hamak/ui-remote-git-fs-impl` |
|
|
184
|
+
| **notification** | Toast notifications |
|
|
185
|
+
|
|
186
|
+
### Data Model
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
Package
|
|
190
|
+
+-- Entity (uuid, name, description, stereotype, status, attributes[], metadata[])
|
|
191
|
+
| +-- Attribute (uuid, name, type, required, constraints, metadata[])
|
|
192
|
+
+-- Relationship (uuid, source, target, cardinality, metadata[])
|
|
193
|
+
+-- Sub-packages
|
|
194
|
+
|
|
195
|
+
Stereotype (id, name, appliesTo, metadataDefinitions[])
|
|
196
|
+
Perspective (uuid, name, rootEntities[], nodes[], maxDepth)
|
|
197
|
+
+-- PerspectiveNode (path, traverse?, exclude?, metadata[])
|
|
198
|
+
+-- ResolvedNode (entityUuid, path, hopDistance, isRoot, isFrontier)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Persistence
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
data-dictionaries/
|
|
205
|
+
microservices/{package}/
|
|
206
|
+
{uuid}_{name}.yaml -- entity files
|
|
207
|
+
{uuid}.comments.yaml -- review comments (sidecar)
|
|
208
|
+
relationships.yaml -- package-level relationships
|
|
209
|
+
metadata.yaml -- package metadata
|
|
210
|
+
perspectives/{uuid}.yaml -- perspective definitions
|
|
211
|
+
stereotypes.yaml -- stereotype definitions
|
|
212
|
+
diagrams/{id}.json -- saved diagram layouts
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
All files are git-tracked for versioning, branching, and collaboration.
|
|
216
|
+
|
|
217
|
+
### API Surface
|
|
218
|
+
|
|
219
|
+
| Area | Endpoints |
|
|
220
|
+
|------|-----------|
|
|
221
|
+
| Entities | CRUD on `/api/services/:service/entities/:entity` |
|
|
222
|
+
| Packages | CRUD on `/api/packages`, hierarchy, path navigation |
|
|
223
|
+
| Relationships | CRUD on `/api/packages/:pkg/relationships` |
|
|
224
|
+
| Stereotypes | CRUD on `/api/stereotypes` |
|
|
225
|
+
| Perspectives | CRUD + resolve + graph on `/api/perspectives` |
|
|
226
|
+
| Search | `GET /api/search?q=...&type=...&service=...&stereotype=...` |
|
|
227
|
+
| Impact | `GET /api/entities/:uuid/impact` |
|
|
228
|
+
| Review | Submit/approve/return + comments on entities |
|
|
229
|
+
| Import | `POST /api/import/json-schema`, `POST /api/import/sql-ddl` |
|
|
230
|
+
| Export | `GET /api/export/json-schema/:service`, `GET /api/export/markdown/:service` |
|
|
231
|
+
| Quality | `GET /api/quality/report` |
|
|
232
|
+
| Git | Full git operations via `/api/git/dictionaries/*` (framework routes) |
|
|
233
|
+
| Version | Commit, history, revert via `/api/commit`, `/api/history` |
|
|
234
|
+
|
|
235
|
+
## Getting Started
|
|
236
|
+
|
|
237
|
+
### Prerequisites
|
|
238
|
+
|
|
239
|
+
- Node.js v18+
|
|
240
|
+
- npm
|
|
241
|
+
|
|
242
|
+
### Quick Start
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Install dependencies
|
|
246
|
+
cd backend && npm install
|
|
247
|
+
cd ../frontend && npm install
|
|
248
|
+
|
|
249
|
+
# Start (two terminals)
|
|
250
|
+
cd backend && npm run dev # port 3001
|
|
251
|
+
cd frontend && npm run dev # port 3000
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
- Frontend: http://localhost:3000
|
|
255
|
+
- Backend API: http://localhost:3001
|
|
256
|
+
- API docs: http://localhost:3001/api-docs
|
|
257
|
+
|
|
258
|
+
### Docker
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
docker-compose up
|
|
262
|
+
# App available at http://localhost:3001
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Dev Credentials
|
|
266
|
+
|
|
267
|
+
| User | Password | Role |
|
|
268
|
+
|------|----------|------|
|
|
269
|
+
| admin | admin123 | ADMIN (full access) |
|
|
270
|
+
| editor | editor123 | EDITOR (create/edit) |
|
|
271
|
+
| viewer | viewer123 | VIEWER (read-only) |
|
|
272
|
+
|
|
273
|
+
### Configuration Profiles
|
|
274
|
+
|
|
275
|
+
Set via `PROFILE` environment variable:
|
|
276
|
+
|
|
277
|
+
| Profile | Auth | Git | Use Case |
|
|
278
|
+
|---------|------|-----|----------|
|
|
279
|
+
| **local** | None | Local only | Single user, desktop |
|
|
280
|
+
| **team** | Basic/JWT | Remote | Small team sharing a repo |
|
|
281
|
+
| **server** | Auth0/SSO | Remote | Organization-wide deployment |
|
|
282
|
+
|
|
283
|
+
## Technologies
|
|
284
|
+
|
|
285
|
+
| Layer | Stack |
|
|
286
|
+
|-------|-------|
|
|
287
|
+
| Backend | Node.js, Express, TypeScript (ESM), YAML, Git |
|
|
288
|
+
| Frontend | React 18, Vite, TypeScript, Tailwind CSS, DaisyUI, Redux |
|
|
289
|
+
| Framework | @hamak/app-framework (microkernel, DI, plugins) |
|
|
290
|
+
| Visualization | Cytoscape.js (dagre + fcose layouts) |
|
|
291
|
+
| Auth | JWT + Auth0 (mock mode for dev) |
|
|
292
|
+
| Deployment | Docker (multi-stage build) |
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "data-dictionary-backend",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Backend for Data Dictionary Management System",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node dist/server.js",
|
|
9
|
+
"dev": "nodemon --exec tsx src/server.ts",
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"test": "jest",
|
|
12
|
+
"lint": "eslint . --ext .ts",
|
|
13
|
+
"test:coverage": "jest --coverage"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@hamak/filesystem-server-api": "^0.5.1",
|
|
17
|
+
"@hamak/filesystem-server-impl": "^0.5.1",
|
|
18
|
+
"@hamak/filesystem-server-spi": "^0.5.1",
|
|
19
|
+
"@hamak/shared-utils": "^0.5.1",
|
|
20
|
+
"@hamak/ui-remote-git-fs-backend": "file:../../app-framework/packages/ui-remote-git-fs/backend/ui-remote-git-fs-backend",
|
|
21
|
+
"@types/jsonwebtoken": "^9.0.9",
|
|
22
|
+
"cors": "^2.8.5",
|
|
23
|
+
"dotenv": "^16.3.1",
|
|
24
|
+
"express": "^4.18.2",
|
|
25
|
+
"jsonschema": "^1.4.1",
|
|
26
|
+
"jsonwebtoken": "^9.0.2",
|
|
27
|
+
"swagger-jsdoc": "^6.2.8",
|
|
28
|
+
"swagger-ui-express": "^5.0.1",
|
|
29
|
+
"yaml": "^2.3.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@jest/globals": "^30.3.0",
|
|
33
|
+
"@types/cors": "^2.8.13",
|
|
34
|
+
"@types/express": "^4.17.17",
|
|
35
|
+
"@types/jest": "^29.5.3",
|
|
36
|
+
"@types/node": "^20.17.50",
|
|
37
|
+
"@types/supertest": "^6.0.3",
|
|
38
|
+
"@types/swagger-jsdoc": "^6.0.4",
|
|
39
|
+
"@types/swagger-ui-express": "^4.1.8",
|
|
40
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
41
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
42
|
+
"eslint": "^8.45.0",
|
|
43
|
+
"jest": "^29.6.2",
|
|
44
|
+
"nodemon": "^3.0.1",
|
|
45
|
+
"supertest": "^7.1.1",
|
|
46
|
+
"ts-jest": "^29.1.1",
|
|
47
|
+
"ts-node": "^10.9.1",
|
|
48
|
+
"tsx": "^4.21.0",
|
|
49
|
+
"typescript": "^5.1.6"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from 'express';
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
|
|
4
|
+
import { UserRole } from '../../middleware/auth.js';
|
|
5
|
+
import app from '../../server.js';
|
|
6
|
+
|
|
7
|
+
// Extend Request type to include user property
|
|
8
|
+
declare global {
|
|
9
|
+
namespace Express {
|
|
10
|
+
interface Request {
|
|
11
|
+
user?: {
|
|
12
|
+
id: string;
|
|
13
|
+
role: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Mock auth middleware to bypass authentication
|
|
20
|
+
jest.mock('../../middleware/auth', () => ({
|
|
21
|
+
UserRole: {
|
|
22
|
+
ADMIN: 'admin',
|
|
23
|
+
EDITOR: 'editor',
|
|
24
|
+
VIEWER: 'viewer',
|
|
25
|
+
},
|
|
26
|
+
authenticate: jest.fn().mockImplementation((_roles: string[]) => (req: Request, _res: Response, next: NextFunction) => {
|
|
27
|
+
req.user = { id: 'test-user', role: 'admin' };
|
|
28
|
+
next();
|
|
29
|
+
}),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
// Mock JWT auth to bypass token verification
|
|
33
|
+
jest.mock('../../middleware/jwtAuth', () => ({
|
|
34
|
+
verifyToken: jest.fn().mockImplementation((req: Request, _res: Response, next: NextFunction) => {
|
|
35
|
+
req.user = { id: 'test-user', role: 'admin' };
|
|
36
|
+
next();
|
|
37
|
+
}),
|
|
38
|
+
authorizeJwt: jest.fn().mockImplementation((_roles: string[]) => (req: Request, _res: Response, next: NextFunction) => {
|
|
39
|
+
req.user = { id: 'test-user', role: 'admin' };
|
|
40
|
+
next();
|
|
41
|
+
}),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
// Mock services (uses __mocks__ directory auto-mocks)
|
|
45
|
+
jest.mock('../../services/dictionaryService');
|
|
46
|
+
jest.mock('../../services/entityService');
|
|
47
|
+
jest.mock('../../services/serviceService');
|
|
48
|
+
jest.mock('../../services/versionService');
|
|
49
|
+
jest.mock('../../utils/logger');
|
|
50
|
+
|
|
51
|
+
describe('API Integration Tests', () => {
|
|
52
|
+
describe('Health & Status', () => {
|
|
53
|
+
it('should return health status', async () => {
|
|
54
|
+
const response = await request(app).get('/health');
|
|
55
|
+
expect(response.status).toBe(200);
|
|
56
|
+
expect(response.body).toHaveProperty('status', 'ok');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('Service Endpoints', () => {
|
|
61
|
+
it('GET /api/services should return service list', async () => {
|
|
62
|
+
const response = await request(app).get('/api/services');
|
|
63
|
+
expect(response.status).toBe(200);
|
|
64
|
+
expect(response.body).toHaveProperty('data');
|
|
65
|
+
expect(Array.isArray(response.body.data)).toBe(true);
|
|
66
|
+
expect(response.body.data).toContain('user-service');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('GET /api/services/:service/entities should return entities', async () => {
|
|
70
|
+
const response = await request(app).get('/api/services/user-service/entities');
|
|
71
|
+
expect(response.status).toBe(200);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('GET /api/services/:service/entities/:entity should return entity schema', async () => {
|
|
75
|
+
const response = await request(app).get('/api/services/user-service/entities/User');
|
|
76
|
+
expect(response.status).toBe(200);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('POST /api/services/:service/entities should create entity', async () => {
|
|
80
|
+
const response = await request(app)
|
|
81
|
+
.post('/api/services/user-service/entities')
|
|
82
|
+
.send({
|
|
83
|
+
uuid: 'a38d1597-cc4f-4934-bb08-c876c023f693',
|
|
84
|
+
name: 'NewEntity',
|
|
85
|
+
description: 'A new entity',
|
|
86
|
+
attributes: [{ uuid: 'b49e2608-dd5f-4045-aa09-d464c234e694', name: 'id', description: 'ID', type: 'string', required: true }],
|
|
87
|
+
});
|
|
88
|
+
expect(response.status).toBe(201);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('PUT /api/services/:service/entities/:entity should update entity', async () => {
|
|
92
|
+
const response = await request(app)
|
|
93
|
+
.put('/api/services/user-service/entities/User')
|
|
94
|
+
.send({
|
|
95
|
+
uuid: 'a38d1597-cc4f-4934-bb08-c876c023f693',
|
|
96
|
+
name: 'User',
|
|
97
|
+
description: 'Updated user',
|
|
98
|
+
attributes: [{ uuid: 'b49e2608-dd5f-4045-aa09-d464c234e694', name: 'id', description: 'ID', type: 'string', required: true }],
|
|
99
|
+
});
|
|
100
|
+
expect(response.status).toBe(200);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('DELETE /api/services/:service/entities/:entity should delete entity', async () => {
|
|
104
|
+
const response = await request(app).delete('/api/services/user-service/entities/User');
|
|
105
|
+
expect(response.status).toBe(200);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('Search & Graph', () => {
|
|
110
|
+
it('GET /api/search should return results', async () => {
|
|
111
|
+
const response = await request(app).get('/api/search?q=user');
|
|
112
|
+
expect(response.status).toBe(200);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('GET /api/graph/:service should return graph data', async () => {
|
|
116
|
+
const response = await request(app).get('/api/graph/user-service');
|
|
117
|
+
expect(response.status).toBe(200);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('Version Control', () => {
|
|
122
|
+
it('POST /api/commit should commit changes', async () => {
|
|
123
|
+
const response = await request(app)
|
|
124
|
+
.post('/api/commit')
|
|
125
|
+
.send({ message: 'Test commit' });
|
|
126
|
+
expect(response.status).toBe(200);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('GET /api/history should return commit history', async () => {
|
|
130
|
+
const response = await request(app).get('/api/history');
|
|
131
|
+
expect(response.status).toBe(200);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('POST /api/revert should revert to commit', async () => {
|
|
135
|
+
const response = await request(app)
|
|
136
|
+
.post('/api/revert')
|
|
137
|
+
.send({ commitHash: 'mock-commit-1' });
|
|
138
|
+
expect(response.status).toBe(200);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('Relationship Endpoints', () => {
|
|
143
|
+
it('GET /api/packages/:packageName/relationships should return relationships', async () => {
|
|
144
|
+
const response = await request(app).get('/api/packages/user-service/relationships');
|
|
145
|
+
expect(response.status).toBe(200);
|
|
146
|
+
expect(response.body).toHaveProperty('data');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
|
|
3
|
+
// Load environment variables for testing
|
|
4
|
+
dotenv.config({ path: '.env.test' });
|
|
5
|
+
|
|
6
|
+
// Mock logger to prevent console output during tests
|
|
7
|
+
jest.mock('../utils/logger', () => ({
|
|
8
|
+
logger: {
|
|
9
|
+
info: jest.fn(),
|
|
10
|
+
error: jest.fn(),
|
|
11
|
+
warn: jest.fn(),
|
|
12
|
+
debug: jest.fn(),
|
|
13
|
+
},
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// Global test setup
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
// Setup any global test requirements here
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Global test teardown
|
|
22
|
+
afterAll(() => {
|
|
23
|
+
// Clean up any global test resources here
|
|
24
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Express } from 'express';
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a test client for making HTTP requests to the Express app
|
|
8
|
+
* @param app Express application
|
|
9
|
+
* @returns Supertest instance
|
|
10
|
+
*/
|
|
11
|
+
export const createTestClient = (app: Express) => {
|
|
12
|
+
return request(app);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a temporary test directory for file operations
|
|
17
|
+
* @param dirPath Directory path to create
|
|
18
|
+
*/
|
|
19
|
+
export const createTestDirectory = (dirPath: string): void => {
|
|
20
|
+
if (!fs.existsSync(dirPath)) {
|
|
21
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Removes a test directory and all its contents
|
|
27
|
+
* @param dirPath Directory path to remove
|
|
28
|
+
*/
|
|
29
|
+
export const removeTestDirectory = (dirPath: string): void => {
|
|
30
|
+
if (fs.existsSync(dirPath)) {
|
|
31
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a test YAML file with the given content
|
|
37
|
+
* @param filePath Path to create the file
|
|
38
|
+
* @param content Content to write to the file
|
|
39
|
+
*/
|
|
40
|
+
export const createTestYamlFile = (filePath: string, content: string): void => {
|
|
41
|
+
const dir = path.dirname(filePath);
|
|
42
|
+
if (!fs.existsSync(dir)) {
|
|
43
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
fs.writeFileSync(filePath, content);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generates a random entity for testing
|
|
50
|
+
* @param overrides Properties to override in the generated entity
|
|
51
|
+
* @returns A test entity
|
|
52
|
+
*/
|
|
53
|
+
export const generateTestEntity = (overrides = {}) => {
|
|
54
|
+
return {
|
|
55
|
+
id: `test-entity-${Date.now()}`,
|
|
56
|
+
name: 'TestEntity',
|
|
57
|
+
description: 'A test entity for unit tests',
|
|
58
|
+
microservice: 'test-service',
|
|
59
|
+
version: '1.0.0',
|
|
60
|
+
attributes: [
|
|
61
|
+
{
|
|
62
|
+
name: 'id',
|
|
63
|
+
description: 'Primary identifier',
|
|
64
|
+
type: 'string',
|
|
65
|
+
required: true,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'name',
|
|
69
|
+
description: 'Entity name',
|
|
70
|
+
type: 'string',
|
|
71
|
+
required: true,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
...overrides,
|
|
75
|
+
};
|
|
76
|
+
};
|