Skip to content

Commit b0f91c2

Browse files
authored
Feat/evault core (#100)
* feat: migrate neo4j * chore: envelope logic works * chore: envelope logic works * feat: parsed envelopes search * feat: generics * feat: protocol * feat: jwt sigs in w3id * chore: stuff works * chore: tests for evault core * chore: format * chore: fix test
1 parent 536c8ee commit b0f91c2

25 files changed

+3781
-88
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Tests [evault-core]
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'infrastructure/evault-core/**'
8+
pull_request:
9+
branches: [main]
10+
paths:
11+
- 'infrastructure/w3id/**'
12+
13+
jobs:
14+
test:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v3
20+
21+
- name: Set up Node.js 22
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: 22
25+
26+
- name: Install pnpm
27+
run: npm install -g pnpm
28+
29+
- name: Install dependencies
30+
run: pnpm install
31+
32+
- name: Run tests
33+
run: pnpm -F=evault-core test
34+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
version: '3.8'
2+
3+
services:
4+
neo4j:
5+
image: neo4j:5.15
6+
container_name: neo4j
7+
ports:
8+
- "7474:7474"
9+
- "7687:7687"
10+
environment:
11+
- NEO4J_AUTH=neo4j/testpass
12+
volumes:
13+
- neo4j_data:/data
14+
networks:
15+
- graphnet
16+
17+
volumes:
18+
neo4j_data:
19+
20+
networks:
21+
graphnet:
22+
driver: bridge
Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
11
{
2-
"name": "evault-core",
3-
"version": "1.0.0",
4-
"description": "",
5-
"main": "index.js",
6-
"scripts": {
7-
"test": "echo \"Error: no test specified\" && exit 1"
8-
},
9-
"keywords": [],
10-
"author": "",
11-
"license": "ISC"
2+
"name": "evault-core",
3+
"version": "0.1.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "vitest --config vitest.config.ts"
8+
},
9+
"keywords": [],
10+
"author": "",
11+
"license": "ISC",
12+
"devDependencies": {
13+
"@types/json-schema": "^7.0.15",
14+
"@types/node": "^22.13.10",
15+
"dotenv": "^16.5.0",
16+
"testcontainers": "^10.24.2",
17+
"tsx": "^4.19.3",
18+
"typescript": "^5.8.3",
19+
"uuid": "^11.1.0",
20+
"vitest": "^3.0.9"
21+
},
22+
"dependencies": {
23+
"@testcontainers/neo4j": "^10.24.2",
24+
"graphql": "^16.10.0",
25+
"graphql-type-json": "^0.3.2",
26+
"graphql-voyager": "^2.1.0",
27+
"graphql-yoga": "^5.13.4",
28+
"json-schema": "^0.4.0",
29+
"neo4j-driver": "^5.28.1",
30+
"w3id": "workspace:*"
31+
}
1232
}
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
import neo4j, { Driver } from "neo4j-driver";
2+
import { DbService } from "./db.service"; // adjust if needed
3+
import { it, describe, beforeAll, afterAll, expect } from "vitest";
4+
import { Neo4jContainer, StartedNeo4jContainer } from "@testcontainers/neo4j";
5+
6+
type Envelope = {
7+
id: string;
8+
ontology: string;
9+
value: any;
10+
valueType: string;
11+
};
12+
13+
describe("DbService (integration)", () => {
14+
let container: StartedNeo4jContainer;
15+
let service: DbService;
16+
let driver: Driver;
17+
18+
beforeAll(async () => {
19+
container = await new Neo4jContainer("neo4j:5.15").start();
20+
21+
const username = container.getUsername();
22+
const password = container.getPassword();
23+
const boltPort = container.getMappedPort(7687);
24+
const uri = `bolt://localhost:${boltPort}`;
25+
26+
driver = neo4j.driver(uri, neo4j.auth.basic(username, password));
27+
service = new DbService(driver);
28+
});
29+
30+
afterAll(async () => {
31+
await service.close();
32+
await driver.close();
33+
await container.stop();
34+
});
35+
36+
it("should store and retrieve a meta-envelope with various data types", async () => {
37+
const input = {
38+
ontology: "TestTypes",
39+
payload: {
40+
string: "hello world",
41+
number: 42,
42+
boolean: true,
43+
date: new Date("2025-04-10T00:00:00Z"),
44+
array: [1, 2, 3],
45+
object: { nested: { value: "deep" } },
46+
},
47+
acl: ["@test-user"],
48+
};
49+
50+
const result = await service.storeMetaEnvelope(input, input.acl);
51+
const id = result.metaEnvelope.id;
52+
53+
const fetched = await service.findMetaEnvelopeById(id);
54+
expect(fetched).toBeDefined();
55+
expect(fetched.id).toBeDefined();
56+
expect(fetched.ontology).toBe("TestTypes");
57+
expect(fetched.acl).toEqual(["@test-user"]);
58+
expect(fetched.envelopes).toHaveLength(6);
59+
60+
// Verify parsed field matches original payload
61+
expect(fetched.parsed).toEqual(input.payload);
62+
63+
// Verify each data type is properly stored and retrieved
64+
const envelopes = fetched.envelopes.reduce(
65+
(acc: Record<string, Envelope>, e: Envelope) => {
66+
acc[e.ontology] = e;
67+
return acc;
68+
},
69+
{},
70+
);
71+
72+
expect(envelopes.string.value).toBe("hello world");
73+
expect(envelopes.string.valueType).toBe("string");
74+
75+
expect(envelopes.number.value).toBe(42);
76+
expect(envelopes.number.valueType).toBe("number");
77+
78+
expect(envelopes.boolean.value).toBe(true);
79+
expect(envelopes.boolean.valueType).toBe("boolean");
80+
81+
expect(envelopes.date.value).toBeInstanceOf(Date);
82+
expect(envelopes.date.value.toISOString()).toBe(
83+
"2025-04-10T00:00:00.000Z",
84+
);
85+
expect(envelopes.date.valueType).toBe("date");
86+
87+
expect(envelopes.array.value).toEqual([1, 2, 3]);
88+
expect(envelopes.array.valueType).toBe("array");
89+
90+
expect(envelopes.object.value).toEqual({ nested: { value: "deep" } });
91+
expect(envelopes.object.valueType).toBe("object");
92+
});
93+
94+
it("should find meta-envelopes containing the search term in any envelope value", async () => {
95+
const input = {
96+
ontology: "SocialMediaPost",
97+
payload: {
98+
text: "This is a searchable tweet",
99+
image: "https://example.com/image.jpg",
100+
likes: ["user1", "user2"],
101+
},
102+
acl: ["@search-test-user"],
103+
};
104+
105+
const metaEnv = await service.storeMetaEnvelope(input, input.acl);
106+
107+
const found = await service.findMetaEnvelopesBySearchTerm(
108+
"SocialMediaPost",
109+
"searchable",
110+
);
111+
112+
expect(Array.isArray(found)).toBe(true);
113+
const match = found.find((m) => m.id === metaEnv.metaEnvelope.id);
114+
expect(match).toBeDefined();
115+
if (!match) throw new Error();
116+
expect(match.envelopes.length).toBeGreaterThan(0);
117+
expect(
118+
match.envelopes.some((e) => e.value.includes("searchable")),
119+
).toBe(true);
120+
});
121+
122+
it("should return empty array if no values contain the search term", async () => {
123+
const found = await service.findMetaEnvelopesBySearchTerm(
124+
"SocialMediaPost",
125+
"notfoundterm",
126+
);
127+
expect(Array.isArray(found)).toBe(true);
128+
expect(found.length).toBe(0);
129+
});
130+
131+
it("should find meta-envelopes by ontology", async () => {
132+
const results =
133+
await service.findMetaEnvelopesByOntology("SocialMediaPost");
134+
expect(Array.isArray(results)).toBe(true);
135+
expect(results.length).toBeGreaterThan(0);
136+
});
137+
138+
it("should delete a meta-envelope and its envelopes", async () => {
139+
const meta = {
140+
ontology: "TempPost",
141+
payload: {
142+
value: "to be deleted",
143+
},
144+
acl: ["@delete-user"],
145+
};
146+
147+
const stored = await service.storeMetaEnvelope(meta, meta.acl);
148+
await service.deleteMetaEnvelope(stored.metaEnvelope.id);
149+
150+
const deleted = await service.findMetaEnvelopeById(
151+
stored.metaEnvelope.id,
152+
);
153+
expect(deleted).toBeNull();
154+
});
155+
156+
it("should update envelope value with proper type handling", async () => {
157+
const meta = {
158+
ontology: "UpdateTest",
159+
payload: {
160+
value: "original",
161+
},
162+
acl: ["@updater"],
163+
};
164+
165+
const stored = await service.storeMetaEnvelope(meta, meta.acl);
166+
167+
const result = await service.findMetaEnvelopeById(
168+
stored.metaEnvelope.id,
169+
);
170+
const targetEnvelope = result.envelopes.find(
171+
(e: Envelope) => e.ontology === "value",
172+
);
173+
174+
// Update with a different type
175+
const newValue = new Date("2025-04-10T00:00:00Z");
176+
await service.updateEnvelopeValue(targetEnvelope.id, newValue);
177+
178+
const updated = await service.findMetaEnvelopeById(
179+
stored.metaEnvelope.id,
180+
);
181+
const updatedValue = updated.envelopes.find(
182+
(e: Envelope) => e.id === targetEnvelope.id,
183+
);
184+
expect(updatedValue.value).toBeInstanceOf(Date);
185+
expect(updatedValue.value.toISOString()).toBe(
186+
"2025-04-10T00:00:00.000Z",
187+
);
188+
expect(updatedValue.valueType).toBe("date");
189+
});
190+
191+
it("should find meta-envelopes containing the search term in any value type", async () => {
192+
const input = {
193+
ontology: "SearchTest",
194+
payload: {
195+
string: "This is a searchable string",
196+
array: ["searchable", "array", "element"],
197+
object: { text: "searchable object" },
198+
number: 42,
199+
date: new Date("2025-04-10T00:00:00Z"),
200+
},
201+
acl: ["@search-test-user"],
202+
};
203+
204+
const metaEnv = await service.storeMetaEnvelope(input, input.acl);
205+
206+
// Test search in string
207+
const foundInString = await service.findMetaEnvelopesBySearchTerm(
208+
"SearchTest",
209+
"searchable string",
210+
);
211+
expect(foundInString.length).toBeGreaterThan(0);
212+
expect(foundInString[0].id).toBe(metaEnv.metaEnvelope.id);
213+
214+
// Test search in array
215+
const foundInArray = await service.findMetaEnvelopesBySearchTerm(
216+
"SearchTest",
217+
"searchable",
218+
);
219+
expect(foundInArray.length).toBeGreaterThan(0);
220+
expect(foundInArray[0].id).toBe(metaEnv.metaEnvelope.id);
221+
222+
// Test search in object
223+
const foundInObject = await service.findMetaEnvelopesBySearchTerm(
224+
"SearchTest",
225+
"searchable object",
226+
);
227+
expect(foundInObject.length).toBeGreaterThan(0);
228+
expect(foundInObject[0].id).toBe(metaEnv.metaEnvelope.id);
229+
});
230+
231+
it("should find meta-envelopes containing the search term with parsed payload", async () => {
232+
const input = {
233+
ontology: "SearchTestHeyyy",
234+
payload: {
235+
string: "This is a searchable string",
236+
array: ["searchable", "array", "element"],
237+
object: { text: "searchable object" },
238+
number: 42,
239+
date: new Date("2025-04-10T00:00:00Z"),
240+
},
241+
acl: ["@search-test-user"],
242+
};
243+
244+
const metaEnv = await service.storeMetaEnvelope(input, input.acl);
245+
246+
// Test search in string
247+
const foundInString = await service.findMetaEnvelopesBySearchTerm(
248+
"SearchTestHeyyy",
249+
"searchable string",
250+
);
251+
expect(foundInString.length).toBeGreaterThan(0);
252+
expect(foundInString[0].id).toBe(metaEnv.metaEnvelope.id);
253+
254+
// Test search in array
255+
const foundInArray = await service.findMetaEnvelopesBySearchTerm(
256+
"SearchTestHeyyy",
257+
"searchable",
258+
);
259+
expect(foundInArray.length).toBeGreaterThan(0);
260+
expect(foundInArray[0].id).toBe(metaEnv.metaEnvelope.id);
261+
262+
// Test search in object
263+
const foundInObject = await service.findMetaEnvelopesBySearchTerm(
264+
"SearchTestHeyyy",
265+
"searchable object",
266+
);
267+
expect(foundInObject.length).toBeGreaterThan(0);
268+
expect(foundInObject[0].id).toBe(metaEnv.metaEnvelope.id);
269+
});
270+
});

0 commit comments

Comments
 (0)