|
| 1 | +import { describe, it, expect, beforeAll, afterAll } from "vitest"; |
| 2 | +import { GraphQLServer } from "../../graphql-server"; |
| 3 | +import { DbService } from "../../../db/db.service"; |
| 4 | +import { createYoga } from "graphql-yoga"; |
| 5 | +import { W3ID } from "w3id"; |
| 6 | +import fetch from "node-fetch"; |
| 7 | + |
| 8 | +// Mock W3ID for testing |
| 9 | +jest.mock("w3id", () => ({ |
| 10 | + W3ID: { |
| 11 | + signJWT: jest.fn().mockResolvedValue("mock.jwt.token"), |
| 12 | + getJWTHeader: jest.fn().mockReturnValue({ kid: "user1#test" }), |
| 13 | + }, |
| 14 | +})); |
| 15 | + |
| 16 | +describe("GraphQL Server E2E", () => { |
| 17 | + let server: any; |
| 18 | + let url: string; |
| 19 | + const testPort = 4001; |
| 20 | + |
| 21 | + // Create test data |
| 22 | + const testData = { |
| 23 | + "meta-1": { |
| 24 | + id: "meta-1", |
| 25 | + ontology: "test", |
| 26 | + acl: ["user1", "user2"], |
| 27 | + envelopes: [{ id: "env-1", ontology: "test", value: { data: "test1" } }], |
| 28 | + parsed: { test: "data1" }, |
| 29 | + }, |
| 30 | + "meta-2": { |
| 31 | + id: "meta-2", |
| 32 | + ontology: "test", |
| 33 | + acl: ["*"], |
| 34 | + envelopes: [{ id: "env-2", ontology: "test", value: { data: "test2" } }], |
| 35 | + parsed: { test: "data2" }, |
| 36 | + }, |
| 37 | + "meta-3": { |
| 38 | + id: "meta-3", |
| 39 | + ontology: "test", |
| 40 | + acl: ["user2"], |
| 41 | + envelopes: [{ id: "env-3", ontology: "test", value: { data: "test3" } }], |
| 42 | + parsed: { test: "data3" }, |
| 43 | + }, |
| 44 | + }; |
| 45 | + |
| 46 | + // Mock DB Service |
| 47 | + class TestDbService implements Partial<DbService> { |
| 48 | + private data = testData; |
| 49 | + |
| 50 | + async findMetaEnvelopeById(id: string) { |
| 51 | + return this.data[id]; |
| 52 | + } |
| 53 | + |
| 54 | + async findMetaEnvelopesByOntology() { |
| 55 | + return Object.values(this.data); |
| 56 | + } |
| 57 | + |
| 58 | + async findMetaEnvelopesBySearchTerm(ontology: string, term: string) { |
| 59 | + return Object.values(this.data).filter((e) => e.ontology === ontology); |
| 60 | + } |
| 61 | + |
| 62 | + async storeMetaEnvelope(input: any) { |
| 63 | + const id = `meta-${Object.keys(this.data).length + 1}`; |
| 64 | + this.data[id] = { |
| 65 | + id, |
| 66 | + ...input, |
| 67 | + envelopes: [], |
| 68 | + parsed: {}, |
| 69 | + }; |
| 70 | + return { |
| 71 | + metaEnvelope: this.data[id], |
| 72 | + envelopes: [], |
| 73 | + }; |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + beforeAll(async () => { |
| 78 | + const db = new TestDbService() as DbService; |
| 79 | + const graphqlServer = new GraphQLServer(db); |
| 80 | + server = createYoga({ |
| 81 | + schema: graphqlServer["schema"], |
| 82 | + context: graphqlServer["createContext"], |
| 83 | + }); |
| 84 | + |
| 85 | + url = `http://localhost:${testPort}/graphql`; |
| 86 | + }); |
| 87 | + |
| 88 | + afterAll(() => { |
| 89 | + server.stop(); |
| 90 | + }); |
| 91 | + |
| 92 | + describe("Queries", () => { |
| 93 | + it("should return meta envelope when user has access", async () => { |
| 94 | + const query = ` |
| 95 | + query { |
| 96 | + getMetaEnvelopeById(id: "meta-1") { |
| 97 | + id |
| 98 | + ontology |
| 99 | + parsed |
| 100 | + } |
| 101 | + } |
| 102 | + `; |
| 103 | + |
| 104 | + const response = await fetch(url, { |
| 105 | + method: "POST", |
| 106 | + headers: { |
| 107 | + "Content-Type": "application/json", |
| 108 | + Authorization: "Bearer user1.jwt.token", |
| 109 | + }, |
| 110 | + body: JSON.stringify({ query }), |
| 111 | + }); |
| 112 | + |
| 113 | + const result = await response.json(); |
| 114 | + expect(result.data.getMetaEnvelopeById).toBeDefined(); |
| 115 | + expect(result.data.getMetaEnvelopeById.id).toBe("meta-1"); |
| 116 | + expect(result.data.getMetaEnvelopeById.acl).toBeUndefined(); |
| 117 | + }); |
| 118 | + |
| 119 | + it("should return null when user doesn't have access", async () => { |
| 120 | + const query = ` |
| 121 | + query { |
| 122 | + getMetaEnvelopeById(id: "meta-3") { |
| 123 | + id |
| 124 | + ontology |
| 125 | + parsed |
| 126 | + } |
| 127 | + } |
| 128 | + `; |
| 129 | + |
| 130 | + const response = await fetch(url, { |
| 131 | + method: "POST", |
| 132 | + headers: { |
| 133 | + "Content-Type": "application/json", |
| 134 | + Authorization: "Bearer user1.jwt.token", |
| 135 | + }, |
| 136 | + body: JSON.stringify({ query }), |
| 137 | + }); |
| 138 | + |
| 139 | + const result = await response.json(); |
| 140 | + expect(result.errors[0].message).toBe("Access denied"); |
| 141 | + }); |
| 142 | + |
| 143 | + it("should filter bulk query results based on ACL", async () => { |
| 144 | + const query = ` |
| 145 | + query { |
| 146 | + searchMetaEnvelopes(ontology: "test", term: "") { |
| 147 | + id |
| 148 | + ontology |
| 149 | + parsed |
| 150 | + } |
| 151 | + } |
| 152 | + `; |
| 153 | + |
| 154 | + const response = await fetch(url, { |
| 155 | + method: "POST", |
| 156 | + headers: { |
| 157 | + "Content-Type": "application/json", |
| 158 | + Authorization: "Bearer user1.jwt.token", |
| 159 | + }, |
| 160 | + body: JSON.stringify({ query }), |
| 161 | + }); |
| 162 | + |
| 163 | + const result = await response.json(); |
| 164 | + expect(result.data.searchMetaEnvelopes).toHaveLength(2); |
| 165 | + expect( |
| 166 | + result.data.searchMetaEnvelopes.find((e) => e.id === "meta-3") |
| 167 | + ).toBeUndefined(); |
| 168 | + }); |
| 169 | + }); |
| 170 | + |
| 171 | + describe("Mutations", () => { |
| 172 | + it("should create meta envelope with ACL", async () => { |
| 173 | + const mutation = ` |
| 174 | + mutation { |
| 175 | + storeMetaEnvelope(input: { |
| 176 | + ontology: "test", |
| 177 | + payload: { test: "data" }, |
| 178 | + acl: ["user1"] |
| 179 | + }) { |
| 180 | + metaEnvelope { |
| 181 | + id |
| 182 | + ontology |
| 183 | + parsed |
| 184 | + } |
| 185 | + } |
| 186 | + } |
| 187 | + `; |
| 188 | + |
| 189 | + const response = await fetch(url, { |
| 190 | + method: "POST", |
| 191 | + headers: { |
| 192 | + "Content-Type": "application/json", |
| 193 | + Authorization: "Bearer user1.jwt.token", |
| 194 | + }, |
| 195 | + body: JSON.stringify({ query: mutation }), |
| 196 | + }); |
| 197 | + |
| 198 | + const result = await response.json(); |
| 199 | + expect(result.data.storeMetaEnvelope.metaEnvelope).toBeDefined(); |
| 200 | + expect(result.data.storeMetaEnvelope.metaEnvelope.acl).toBeUndefined(); |
| 201 | + }); |
| 202 | + |
| 203 | + it("should prevent updating envelope without access", async () => { |
| 204 | + const mutation = ` |
| 205 | + mutation { |
| 206 | + updateEnvelopeValue( |
| 207 | + envelopeId: "meta-3", |
| 208 | + newValue: { test: "updated" } |
| 209 | + ) |
| 210 | + } |
| 211 | + `; |
| 212 | + |
| 213 | + const response = await fetch(url, { |
| 214 | + method: "POST", |
| 215 | + headers: { |
| 216 | + "Content-Type": "application/json", |
| 217 | + Authorization: "Bearer user1.jwt.token", |
| 218 | + }, |
| 219 | + body: JSON.stringify({ query: mutation }), |
| 220 | + }); |
| 221 | + |
| 222 | + const result = await response.json(); |
| 223 | + expect(result.errors[0].message).toBe("Access denied"); |
| 224 | + }); |
| 225 | + }); |
| 226 | +}); |
0 commit comments