-
Notifications
You must be signed in to change notification settings - Fork 2
Feat/evault core #100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/evault core #100
Changes from all commits
a34ee4f
4c502c7
fccd3cc
e55d757
22e1c8f
96e842e
606b22d
d71b44c
35dee92
b1ccbb5
7c5c44a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
name: Tests [evault-core] | ||
|
||
on: | ||
push: | ||
branches: [main] | ||
paths: | ||
- 'infrastructure/evault-core/**' | ||
pull_request: | ||
branches: [main] | ||
paths: | ||
- 'infrastructure/w3id/**' | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v3 | ||
|
||
- name: Set up Node.js 22 | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 22 | ||
|
||
- name: Install pnpm | ||
run: npm install -g pnpm | ||
|
||
- name: Install dependencies | ||
run: pnpm install | ||
|
||
- name: Run tests | ||
run: pnpm -F=evault-core test | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
version: '3.8' | ||
|
||
services: | ||
neo4j: | ||
image: neo4j:5.15 | ||
container_name: neo4j | ||
ports: | ||
- "7474:7474" | ||
- "7687:7687" | ||
environment: | ||
- NEO4J_AUTH=neo4j/testpass | ||
volumes: | ||
- neo4j_data:/data | ||
networks: | ||
- graphnet | ||
|
||
volumes: | ||
neo4j_data: | ||
|
||
networks: | ||
graphnet: | ||
driver: bridge |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,32 @@ | ||
{ | ||
"name": "evault-core", | ||
"version": "1.0.0", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"keywords": [], | ||
"author": "", | ||
"license": "ISC" | ||
"name": "evault-core", | ||
"version": "0.1.0", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "vitest --config vitest.config.ts" | ||
}, | ||
"keywords": [], | ||
"author": "", | ||
"license": "ISC", | ||
"devDependencies": { | ||
"@types/json-schema": "^7.0.15", | ||
"@types/node": "^22.13.10", | ||
"dotenv": "^16.5.0", | ||
"testcontainers": "^10.24.2", | ||
"tsx": "^4.19.3", | ||
"typescript": "^5.8.3", | ||
"uuid": "^11.1.0", | ||
"vitest": "^3.0.9" | ||
}, | ||
"dependencies": { | ||
"@testcontainers/neo4j": "^10.24.2", | ||
"graphql": "^16.10.0", | ||
"graphql-type-json": "^0.3.2", | ||
"graphql-voyager": "^2.1.0", | ||
"graphql-yoga": "^5.13.4", | ||
"json-schema": "^0.4.0", | ||
"neo4j-driver": "^5.28.1", | ||
"w3id": "workspace:*" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
import neo4j, { Driver } from "neo4j-driver"; | ||
import { DbService } from "./db.service"; // adjust if needed | ||
import { it, describe, beforeAll, afterAll, expect } from "vitest"; | ||
import { Neo4jContainer, StartedNeo4jContainer } from "@testcontainers/neo4j"; | ||
|
||
type Envelope = { | ||
id: string; | ||
ontology: string; | ||
value: any; | ||
valueType: string; | ||
}; | ||
|
||
describe("DbService (integration)", () => { | ||
let container: StartedNeo4jContainer; | ||
let service: DbService; | ||
let driver: Driver; | ||
|
||
beforeAll(async () => { | ||
container = await new Neo4jContainer("neo4j:5.15").start(); | ||
|
||
const username = container.getUsername(); | ||
const password = container.getPassword(); | ||
const boltPort = container.getMappedPort(7687); | ||
const uri = `bolt://localhost:${boltPort}`; | ||
|
||
driver = neo4j.driver(uri, neo4j.auth.basic(username, password)); | ||
service = new DbService(driver); | ||
}); | ||
|
||
afterAll(async () => { | ||
await service.close(); | ||
await driver.close(); | ||
await container.stop(); | ||
}); | ||
|
||
it("should store and retrieve a meta-envelope with various data types", async () => { | ||
const input = { | ||
ontology: "TestTypes", | ||
payload: { | ||
string: "hello world", | ||
number: 42, | ||
boolean: true, | ||
date: new Date("2025-04-10T00:00:00Z"), | ||
array: [1, 2, 3], | ||
object: { nested: { value: "deep" } }, | ||
}, | ||
acl: ["@test-user"], | ||
}; | ||
|
||
const result = await service.storeMetaEnvelope(input, input.acl); | ||
const id = result.metaEnvelope.id; | ||
|
||
const fetched = await service.findMetaEnvelopeById(id); | ||
expect(fetched).toBeDefined(); | ||
expect(fetched.id).toBeDefined(); | ||
expect(fetched.ontology).toBe("TestTypes"); | ||
expect(fetched.acl).toEqual(["@test-user"]); | ||
expect(fetched.envelopes).toHaveLength(6); | ||
|
||
// Verify parsed field matches original payload | ||
expect(fetched.parsed).toEqual(input.payload); | ||
|
||
// Verify each data type is properly stored and retrieved | ||
const envelopes = fetched.envelopes.reduce( | ||
(acc: Record<string, Envelope>, e: Envelope) => { | ||
acc[e.ontology] = e; | ||
return acc; | ||
}, | ||
{}, | ||
); | ||
|
||
expect(envelopes.string.value).toBe("hello world"); | ||
expect(envelopes.string.valueType).toBe("string"); | ||
|
||
expect(envelopes.number.value).toBe(42); | ||
expect(envelopes.number.valueType).toBe("number"); | ||
|
||
expect(envelopes.boolean.value).toBe(true); | ||
expect(envelopes.boolean.valueType).toBe("boolean"); | ||
|
||
expect(envelopes.date.value).toBeInstanceOf(Date); | ||
expect(envelopes.date.value.toISOString()).toBe( | ||
"2025-04-10T00:00:00.000Z", | ||
); | ||
expect(envelopes.date.valueType).toBe("date"); | ||
|
||
expect(envelopes.array.value).toEqual([1, 2, 3]); | ||
expect(envelopes.array.valueType).toBe("array"); | ||
|
||
expect(envelopes.object.value).toEqual({ nested: { value: "deep" } }); | ||
expect(envelopes.object.valueType).toBe("object"); | ||
}); | ||
|
||
it("should find meta-envelopes containing the search term in any envelope value", async () => { | ||
const input = { | ||
ontology: "SocialMediaPost", | ||
payload: { | ||
text: "This is a searchable tweet", | ||
image: "https://example.com/image.jpg", | ||
likes: ["user1", "user2"], | ||
}, | ||
acl: ["@search-test-user"], | ||
}; | ||
|
||
const metaEnv = await service.storeMetaEnvelope(input, input.acl); | ||
|
||
const found = await service.findMetaEnvelopesBySearchTerm( | ||
"SocialMediaPost", | ||
"searchable", | ||
); | ||
|
||
expect(Array.isArray(found)).toBe(true); | ||
const match = found.find((m) => m.id === metaEnv.metaEnvelope.id); | ||
expect(match).toBeDefined(); | ||
if (!match) throw new Error(); | ||
expect(match.envelopes.length).toBeGreaterThan(0); | ||
expect( | ||
match.envelopes.some((e) => e.value.includes("searchable")), | ||
).toBe(true); | ||
}); | ||
|
||
it("should return empty array if no values contain the search term", async () => { | ||
const found = await service.findMetaEnvelopesBySearchTerm( | ||
"SocialMediaPost", | ||
"notfoundterm", | ||
); | ||
expect(Array.isArray(found)).toBe(true); | ||
expect(found.length).toBe(0); | ||
}); | ||
|
||
it("should find meta-envelopes by ontology", async () => { | ||
const results = | ||
await service.findMetaEnvelopesByOntology("SocialMediaPost"); | ||
expect(Array.isArray(results)).toBe(true); | ||
expect(results.length).toBeGreaterThan(0); | ||
}); | ||
|
||
it("should delete a meta-envelope and its envelopes", async () => { | ||
const meta = { | ||
ontology: "TempPost", | ||
payload: { | ||
value: "to be deleted", | ||
}, | ||
acl: ["@delete-user"], | ||
}; | ||
|
||
const stored = await service.storeMetaEnvelope(meta, meta.acl); | ||
await service.deleteMetaEnvelope(stored.metaEnvelope.id); | ||
|
||
const deleted = await service.findMetaEnvelopeById( | ||
stored.metaEnvelope.id, | ||
); | ||
expect(deleted).toBeNull(); | ||
}); | ||
|
||
it("should update envelope value with proper type handling", async () => { | ||
const meta = { | ||
ontology: "UpdateTest", | ||
payload: { | ||
value: "original", | ||
}, | ||
acl: ["@updater"], | ||
}; | ||
|
||
const stored = await service.storeMetaEnvelope(meta, meta.acl); | ||
|
||
const result = await service.findMetaEnvelopeById( | ||
stored.metaEnvelope.id, | ||
); | ||
const targetEnvelope = result.envelopes.find( | ||
(e: Envelope) => e.ontology === "value", | ||
); | ||
|
||
// Update with a different type | ||
const newValue = new Date("2025-04-10T00:00:00Z"); | ||
await service.updateEnvelopeValue(targetEnvelope.id, newValue); | ||
|
||
const updated = await service.findMetaEnvelopeById( | ||
stored.metaEnvelope.id, | ||
); | ||
const updatedValue = updated.envelopes.find( | ||
(e: Envelope) => e.id === targetEnvelope.id, | ||
); | ||
expect(updatedValue.value).toBeInstanceOf(Date); | ||
expect(updatedValue.value.toISOString()).toBe( | ||
"2025-04-10T00:00:00.000Z", | ||
); | ||
expect(updatedValue.valueType).toBe("date"); | ||
}); | ||
|
||
it("should find meta-envelopes containing the search term in any value type", async () => { | ||
const input = { | ||
ontology: "SearchTest", | ||
payload: { | ||
string: "This is a searchable string", | ||
array: ["searchable", "array", "element"], | ||
object: { text: "searchable object" }, | ||
number: 42, | ||
date: new Date("2025-04-10T00:00:00Z"), | ||
}, | ||
acl: ["@search-test-user"], | ||
}; | ||
|
||
const metaEnv = await service.storeMetaEnvelope(input, input.acl); | ||
|
||
// Test search in string | ||
const foundInString = await service.findMetaEnvelopesBySearchTerm( | ||
"SearchTest", | ||
"searchable string", | ||
); | ||
expect(foundInString.length).toBeGreaterThan(0); | ||
expect(foundInString[0].id).toBe(metaEnv.metaEnvelope.id); | ||
|
||
// Test search in array | ||
const foundInArray = await service.findMetaEnvelopesBySearchTerm( | ||
"SearchTest", | ||
"searchable", | ||
); | ||
expect(foundInArray.length).toBeGreaterThan(0); | ||
expect(foundInArray[0].id).toBe(metaEnv.metaEnvelope.id); | ||
|
||
// Test search in object | ||
const foundInObject = await service.findMetaEnvelopesBySearchTerm( | ||
"SearchTest", | ||
"searchable object", | ||
); | ||
expect(foundInObject.length).toBeGreaterThan(0); | ||
expect(foundInObject[0].id).toBe(metaEnv.metaEnvelope.id); | ||
}); | ||
|
||
it("should find meta-envelopes containing the search term with parsed payload", async () => { | ||
const input = { | ||
ontology: "SearchTestHeyyy", | ||
payload: { | ||
string: "This is a searchable string", | ||
array: ["searchable", "array", "element"], | ||
object: { text: "searchable object" }, | ||
number: 42, | ||
date: new Date("2025-04-10T00:00:00Z"), | ||
}, | ||
acl: ["@search-test-user"], | ||
}; | ||
|
||
const metaEnv = await service.storeMetaEnvelope(input, input.acl); | ||
|
||
// Test search in string | ||
const foundInString = await service.findMetaEnvelopesBySearchTerm( | ||
"SearchTestHeyyy", | ||
"searchable string", | ||
); | ||
expect(foundInString.length).toBeGreaterThan(0); | ||
expect(foundInString[0].id).toBe(metaEnv.metaEnvelope.id); | ||
|
||
// Test search in array | ||
const foundInArray = await service.findMetaEnvelopesBySearchTerm( | ||
"SearchTestHeyyy", | ||
"searchable", | ||
); | ||
expect(foundInArray.length).toBeGreaterThan(0); | ||
expect(foundInArray[0].id).toBe(metaEnv.metaEnvelope.id); | ||
|
||
// Test search in object | ||
const foundInObject = await service.findMetaEnvelopesBySearchTerm( | ||
"SearchTestHeyyy", | ||
"searchable object", | ||
); | ||
expect(foundInObject.length).toBeGreaterThan(0); | ||
expect(foundInObject[0].id).toBe(metaEnv.metaEnvelope.id); | ||
}); | ||
}); | ||
Comment on lines
+191
to
+270
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Eliminate redundant tests. This test is almost identical to the search test at lines 94-120, and the test at lines 231-269 is nearly identical to the test at lines 191-230. Consider consolidating these tests to reduce duplication. Consider consolidating the duplicate tests by:
For example: function testSearchFunctionality(ontology: string, description: string) {
it(`should ${description} for ontology ${ontology}`, async () => {
// Common test logic
});
}
testSearchFunctionality("SearchTest", "find meta-envelopes containing the search term in any value type");
testSearchFunctionality("SearchTestHeyyy", "find meta-envelopes containing the search term with parsed payload"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Update the checkout action to the latest version
The current version of
actions/checkout
being used (v3) is outdated. The latest version (v4) includes improvements and bug fixes.📝 Committable suggestion
🧰 Tools
🪛 actionlint (1.7.4)
19-19: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)