This commit is contained in:
Oleg Maslov
2026-09-02 10:14:22 +02:00
parent 0c3e2ead3b
commit b20b138fe4
771 changed files with 161561 additions and 9027 deletions

View File

@@ -1,7 +1,7 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { buildServer } from '../../src/index.js';
import { users, teams, teamMembers, teamEntities, teamRelations } from '../../src/db/schema.js';
import { sql } from 'drizzle-orm';
import { eq, or, sql } from 'drizzle-orm';
describe('Team Knowledge Graph API', () => {
let server: Awaited<ReturnType<typeof buildServer>>;
@@ -10,6 +10,7 @@ describe('Team Knowledge Graph API', () => {
let outsiderId: string;
let teamSlug: string;
let teamId: string;
let foreignEntityId: string;
beforeAll(async () => {
server = await buildServer();
@@ -60,6 +61,25 @@ describe('Team Knowledge Graph API', () => {
{ teamId, userId: memberId, role: 'member' },
]);
const [foreignTeam] = await server.db.insert(teams).values({
name: 'Foreign KG Team',
slug: 'kgtest-foreign-knowledge',
ownerId: outsiderId,
}).returning();
await server.db.insert(teamMembers).values({
teamId: foreignTeam.id,
userId: outsiderId,
role: 'owner',
});
const [foreignEntity] = await server.db.insert(teamEntities).values({
teamId: foreignTeam.id,
entityType: 'secret',
name: 'Foreign Entity',
properties: { confidential: true },
sharedBy: outsiderId,
}).returning();
foreignEntityId = foreignEntity.id;
// Override auth handler
server._authHandler.fn = async function (request, reply) {
const testUserId = request.headers['x-test-user-id'] as string;
@@ -189,6 +209,48 @@ describe('Team Knowledge Graph API', () => {
expect(body.teamId).toBe(teamId);
});
it('rejects relations when either endpoint belongs to another team', async () => {
const localRes = await server.inject({
method: 'POST',
url: `/api/teams/${teamSlug}/entities`,
headers: { 'x-test-user-id': ownerId },
payload: { entityType: 'concept', name: 'Local Endpoint' },
});
const localEntity = localRes.json();
const foreignSource = await server.inject({
method: 'POST',
url: `/api/teams/${teamSlug}/relations`,
headers: { 'x-test-user-id': ownerId },
payload: {
sourceId: foreignEntityId,
targetId: localEntity.id,
relationType: 'references',
},
});
const foreignTarget = await server.inject({
method: 'POST',
url: `/api/teams/${teamSlug}/relations`,
headers: { 'x-test-user-id': ownerId },
payload: {
sourceId: localEntity.id,
targetId: foreignEntityId,
relationType: 'references',
},
});
expect(foreignSource.statusCode).toBe(404);
expect(foreignSource.json()).toEqual({ error: 'Entity not found' });
expect(foreignTarget.statusCode).toBe(404);
expect(foreignTarget.json()).toEqual({ error: 'Entity not found' });
const leaked = await server.db.select().from(teamRelations).where(or(
eq(teamRelations.sourceId, foreignEntityId),
eq(teamRelations.targetId, foreignEntityId),
));
expect(leaked).toEqual([]);
});
it('graph traversal returns connected entities up to depth N', async () => {
// Create a chain: A -> B -> C
const resA = await server.inject({
@@ -259,6 +321,19 @@ describe('Team Knowledge Graph API', () => {
expect(graph2.relations.length).toBeGreaterThanOrEqual(2);
});
it('does not return a foreign-team graph start entity', async () => {
const response = await server.inject({
method: 'GET',
url: `/api/teams/${teamSlug}/graph?startId=${foreignEntityId}&depth=1`,
headers: { 'x-test-user-id': ownerId },
});
expect(response.statusCode).toBe(404);
expect(response.json()).toEqual({ error: 'Entity not found' });
expect(response.body).not.toContain('Foreign Entity');
expect(response.body).not.toContain('confidential');
});
it('shared_by tracks who contributed', async () => {
// Owner creates entity
const res1 = await server.inject({