Skip to content

Commit de5b5c0

Browse files
committed
Added some tests for acl module
Added custom matchers to easy check A) whether two graphs are equal, and B) whether a graph is contained within another
1 parent cfb738b commit de5b5c0

18 files changed

Lines changed: 711 additions & 252 deletions

__mocks__/rdflib.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export function fetcher (store: any) {
5656

5757
export class Fetcher {
5858
requested: any
59+
nonexistent = {}
5960

6061
constructor () {
6162
this.requested = {}

src/acl/acl.ts

Lines changed: 49 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as $rdf from 'rdflib'
99
import ns from '../ns'
1010
import kb from '../store.js'
1111
import utils from '../utils'
12-
import { AgentMapMap, ComboList } from './types'
12+
import { AgentMapMap, AgentMapUnion, ComboList } from './types'
1313
import * as debug from '../debug'
1414

1515
/**
@@ -22,49 +22,38 @@ export function adoptACLDefault (
2222
doc: $rdf.NamedNode,
2323
aclDoc: $rdf.NamedNode,
2424
defaultResource: $rdf.NamedNode,
25-
defaultACLdoc: $rdf.NamedNode
25+
defaultACLDoc: $rdf.NamedNode
2626
): $rdf.IndexedFormula {
2727
const ACL = ns.acl
2828
const isContainer = doc.uri.slice(-1) === '/' // Give default for all directories
29+
2930
const defaults = kb
30-
.each(undefined, ACL('default'), defaultResource, defaultACLdoc)
31-
.concat(
32-
kb.each(undefined, ACL('defaultForNew'), defaultResource, defaultACLdoc)
33-
)
34-
let proposed: Array<$rdf.Statement> = []
35-
defaults.map(function (da) {
36-
proposed = proposed
37-
.concat(kb.statementsMatching(da, ACL('agent'), undefined, defaultACLdoc))
38-
.concat(kb.statementsMatching(da, ACL('agentClass'), undefined, defaultACLdoc))
39-
.concat(kb.statementsMatching(da, ACL('agentGroup'), undefined, defaultACLdoc))
40-
.concat(kb.statementsMatching(da, ACL('origin'), undefined, defaultACLdoc))
41-
.concat(kb.statementsMatching(da, ACL('originClass'), undefined, defaultACLdoc))
42-
.concat(kb.statementsMatching(da, ACL('mode'), undefined, defaultACLdoc))
43-
proposed.push($rdf.st(da, ACL('accessTo'), doc, defaultACLdoc)) // Suppose
44-
if (isContainer) {
45-
// By default, make this apply to folder contents too
46-
proposed.push($rdf.st(da, ACL('default'), doc, defaultACLdoc))
47-
}
48-
})
49-
const kb2 = $rdf.graph() // Potential - derived is kept apart
50-
proposed.map(function (st) {
51-
const move = function (sym) {
52-
const y = defaultACLdoc.uri.length // The default ACL file
53-
return $rdf.sym(
54-
sym.uri.slice(0, y) === defaultACLdoc.uri
55-
? aclDoc.uri + sym.uri.slice(y)
56-
: sym.uri
57-
)
58-
}
59-
kb2.add(
60-
move(st.subject),
61-
move(st.predicate),
62-
move(st.object),
63-
$rdf.sym(aclDoc.uri)
64-
)
65-
})
31+
.each(undefined, ACL('default'), defaultResource, defaultACLDoc)
32+
.concat(kb.each(undefined, ACL('defaultForNew'), defaultResource, defaultACLDoc))
33+
34+
const proposed = defaults.reduce((accumulatedStatements, da) => accumulatedStatements
35+
.concat(kb.statementsMatching(da, ns.rdf('type'), ACL('Authorization'), defaultACLDoc))
36+
.concat(kb.statementsMatching(da, ACL('agent'), undefined, defaultACLDoc))
37+
.concat(kb.statementsMatching(da, ACL('agentClass'), undefined, defaultACLDoc))
38+
.concat(kb.statementsMatching(da, ACL('agentGroup'), undefined, defaultACLDoc))
39+
.concat(kb.statementsMatching(da, ACL('origin'), undefined, defaultACLDoc))
40+
.concat(kb.statementsMatching(da, ACL('originClass'), undefined, defaultACLDoc))
41+
.concat(kb.statementsMatching(da, ACL('mode'), undefined, defaultACLDoc))
42+
.concat($rdf.st(da, ACL('accessTo'), doc, defaultACLDoc))
43+
.concat(isContainer ? $rdf.st(da, ACL('default'), doc, defaultACLDoc) : []), [])
6644

45+
const kb2 = $rdf.graph() // Potential - derived is kept apart
46+
proposed.forEach(st => kb2.add(move(st.subject), move(st.predicate), move(st.object), $rdf.sym(aclDoc.uri)))
6747
return kb2
48+
49+
function move (sym) {
50+
const y = defaultACLDoc.uri.length // The default ACL file
51+
return $rdf.sym(
52+
sym.uri.slice(0, y) === defaultACLDoc.uri
53+
? aclDoc.uri + sym.uri.slice(y)
54+
: sym.uri
55+
)
56+
}
6857
}
6958

7059
/**
@@ -73,14 +62,14 @@ export function adoptACLDefault (
7362
* Accumulate the access rights which each agent or class has
7463
*/
7564
export function readACL (
76-
x: $rdf.NamedNode,
65+
doc: $rdf.NamedNode,
7766
aclDoc: $rdf.NamedNode,
7867
kb2: $rdf.IndexedFormula = kb,
79-
getDefaults?: boolean
68+
getDefaults: boolean = false
8069
): AgentMapMap {
8170
const auths: Array<$rdf.NamedNode> = getDefaults
8271
? getDefaultsFallback(kb2, ns)
83-
: kb2.each(undefined, ns.acl('accessTo'), x)
72+
: kb2.each(undefined, ns.acl('accessTo'), doc)
8473

8574
const ACL = ns.acl
8675
const ac = {
@@ -104,15 +93,15 @@ export function readACL (
10493

10594
function getDefaultsFallback (kb, ns) {
10695
return kb
107-
.each(undefined, ns.acl('default'), x)
108-
.concat(kb.each(undefined, ns.acl('defaultForNew'), x))
96+
.each(undefined, ns.acl('default'), doc)
97+
.concat(kb.each(undefined, ns.acl('defaultForNew'), doc))
10998
}
11099
}
111100

112101
/**
113102
* Compare two ACLs
114103
*/
115-
export function sameACL (a: AgentMapMap, b: AgentMapMap): boolean {
104+
export function sameACL (a: AgentMapMap | AgentMapUnion, b: AgentMapMap | AgentMapUnion): boolean {
116105
const contains = function (a, b) {
117106
for (const pred in {
118107
agent: true,
@@ -139,8 +128,8 @@ export function sameACL (a: AgentMapMap, b: AgentMapMap): boolean {
139128
/**
140129
* Union N ACLs
141130
*/
142-
export function ACLunion (list: Array<AgentMapMap>): AgentMapMap {
143-
const b = list[0]
131+
export function ACLunion (list: Array<AgentMapMap>): AgentMapUnion {
132+
const b = JSON.parse(JSON.stringify(list[0])) // in order to not mutate the original object
144133
let a, ag
145134
for (let k = 1; k < list.length; k++) {
146135
;['agent', 'agentClass', 'agentGroup', 'origin', 'originClass'].map(
@@ -163,7 +152,7 @@ export function ACLunion (list: Array<AgentMapMap>): AgentMapMap {
163152
/**
164153
* Merge ACLs lists from things to form union
165154
*/
166-
export function loadUnionACL (subjectList: Array<$rdf.NamedNode>, callbackFunction: Function): void {
155+
export function loadUnionACL (subjectList: Array<$rdf.NamedNode>, callbackFunction: loadUnionACLCallback): void {
167156
const aclList: Array<AgentMapMap> = []
168157
const doList = function (list) {
169158
if (list.length) {
@@ -192,13 +181,15 @@ export function loadUnionACL (subjectList: Array<$rdf.NamedNode>, callbackFuncti
192181
doList(subjectList)
193182
}
194183

184+
type loadUnionACLCallback = (ok: boolean, message?: string | $rdf.NamedNode | AgentMapUnion | AgentMapMap) => void
185+
195186
/**
196187
* Represents these as an RDF graph by combination of modes
197188
*
198189
* Each agent can only be in one place in this model, one combination of modes.
199190
* Combos are like full control, read append, read only etc.
200191
*/
201-
export function ACLbyCombination (ac: AgentMapMap): ComboList {
192+
export function ACLbyCombination (ac: AgentMapMap | AgentMapUnion): ComboList {
202193
const byCombo = {}
203194
;['agent', 'agentClass', 'agentGroup', 'origin', 'originClass'].map(function (pred) {
204195
for (const agent in ac[pred]) {
@@ -313,9 +304,9 @@ export function makeACLString (x: $rdf.NamedNode, ac: AgentMapMap, aclDoc: $rdf.
313304
export function putACLObject (
314305
kb: $rdf.IndexedFormula,
315306
x: $rdf.NamedNode,
316-
ac: AgentMapMap,
307+
ac: AgentMapMap | AgentMapUnion,
317308
aclDoc: $rdf.NamedNode,
318-
callbackFunction
309+
callbackFunction: (ok: boolean, message?: string) => void
319310
): void {
320311
const byCombo = ACLbyCombination(ac)
321312
return putACLbyCombo(kb, x, byCombo, aclDoc, callbackFunction)
@@ -358,7 +349,7 @@ export function putACLbyCombo (
358349
*
359350
* All group files must be loaded first
360351
*/
361-
export function fixIndividualCardACL (person: $rdf.NamedNode, log: Function, callbackFunction: Function): void {
352+
export function fixIndividualCardACL (person: $rdf.NamedNode, log: Function, callbackFunction: fixIndividualCardACL): void {
362353
const groups = kb.each(undefined, ns.vcard('hasMember'), person)
363354
// const doc = person.doc()
364355
if (groups) {
@@ -370,10 +361,12 @@ export function fixIndividualCardACL (person: $rdf.NamedNode, log: Function, cal
370361
// @@ if no groups, then use default for People container or the book top container.?
371362
}
372363

364+
type fixIndividualCardACL = (ok: boolean, message?: string | $rdf.NamedNode | AgentMapUnion | AgentMapMap) => void
365+
373366
/**
374367
* This function is used by [[fixIndividualCardACL]]
375368
*/
376-
export function fixIndividualACL (item: $rdf.NamedNode, subjects: Array<$rdf.NamedNode>, log: Function, callbackFunction: Function): void {
369+
export function fixIndividualACL (item: $rdf.NamedNode, subjects: Array<$rdf.NamedNode>, log: Function, callbackFunction: fixIndividualACLCallback): void {
377370
log = log || debug.log
378371
const doc = item.doc()
379372
getACLorDefault(doc, function (
@@ -390,7 +383,7 @@ export function fixIndividualACL (item: $rdf.NamedNode, subjects: Array<$rdf.Nam
390383
: readACL(defaultHolder, defaultACLDoc)
391384
loadUnionACL(subjects, function (ok, union) {
392385
if (!ok) return callbackFunction(false, union)
393-
if (sameACL(union, ac)) {
386+
if (sameACL(union as AgentMapMap | AgentMapUnion, ac)) {
394387
log('Nice - same ACL. no change ' + utils.label(item) + ' ' + doc)
395388
} else {
396389
log('Group ACLs differ for ' + utils.label(item) + ' ' + doc)
@@ -402,7 +395,7 @@ export function fixIndividualACL (item: $rdf.NamedNode, subjects: Array<$rdf.Nam
402395
putACLObject(
403396
kb,
404397
targetDoc as $rdf.NamedNode,
405-
union,
398+
union as AgentMapMap | AgentMapUnion,
406399
targetACLDoc as $rdf.NamedNode,
407400
callbackFunction
408401
)
@@ -411,6 +404,8 @@ export function fixIndividualACL (item: $rdf.NamedNode, subjects: Array<$rdf.Nam
411404
})
412405
}
413406

407+
type fixIndividualACLCallback = (ok: boolean, message?: string | $rdf.NamedNode | AgentMapUnion | AgentMapMap) => void
408+
414409
/**
415410
* Set an ACL
416411
*/

src/acl/types.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,26 @@
55

66
import { NamedNode } from 'rdflib'
77

8-
export type AgentMapMap = {
9-
agent: AgentMap,
10-
agentClass: AgentMap,
11-
agentGroup: AgentMap,
12-
origin: AgentMap,
13-
originClass: AgentMap
8+
export type AgentMapMap<T = AgentMap> = {
9+
agent: T,
10+
agentClass: T,
11+
agentGroup: T,
12+
origin: T,
13+
originClass: T
1414
}
1515

16+
export type AgentMapUnion = AgentMapMap<AgentUnion>
17+
1618
export type AgentMap = {
1719
[agentUri: string]: {
1820
[modeUri: string]: NamedNode
1921
}
2022
}
2123

24+
export type AgentUnion = {
25+
[agentUri: string]: true | []
26+
}
27+
2228
export type ComboList = { [key: string]: Array<string[]> }
2329

2430
export type PartialAgentTriple = {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { IndexedFormula, NamedNode } from 'rdflib'
2+
3+
export function toContainGraph (received: IndexedFormula, expected: IndexedFormula) {
4+
const receivedStatements = received.statements.slice()
5+
const expectedStatements = expected.statements.slice()
6+
const diffStatements = expectedStatements.filter(st => !received.holds(st.subject, st.predicate, st.object, st.why as NamedNode))
7+
return {
8+
pass: diffStatements.length === 0,
9+
message: () => `Statements in expected graph is not contained in received graph\n
10+
Found ${receivedStatements.length} statements in received graph:
11+
${receivedStatements.map(st => `- ${st.subject} ${st.predicate} ${st.object} ${st.why} .\n`).join('')}
12+
The following ${diffStatements.length} statements does not exist in the above graph:
13+
${diffStatements.map(st => `- ${st.subject} ${st.predicate} ${st.object} ${st.why} .\n`).join('')}`
14+
}
15+
}
16+
17+
declare global {
18+
namespace jest {
19+
interface Matchers<R> {
20+
toContainGraph (a: IndexedFormula): R;
21+
}
22+
}
23+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { IndexedFormula, NamedNode } from 'rdflib'
2+
3+
export function toEqualGraph (received: IndexedFormula, expected: IndexedFormula) {
4+
const receivedStatements = received.statements.slice()
5+
const expectedStatements = expected.statements.slice()
6+
if (receivedStatements.length !== expectedStatements.length) {
7+
return {
8+
pass: false,
9+
message: () => `Expected graph does not equal to received graph\n
10+
Found ${receivedStatements.length} statements in received graph:
11+
${receivedStatements.map(st => `- ${st.subject} ${st.predicate} ${st.object} ${st.why} .\n`).join('')}
12+
Found ${expectedStatements.length} statements in expected graph:
13+
${expectedStatements.map(st => `- ${st.subject} ${st.predicate} ${st.object} ${st.why} .\n`).join('')}`
14+
}
15+
}
16+
const expDiffRecStatements = expectedStatements.filter(st => !received.holds(st.subject, st.predicate, st.object, st.why as NamedNode))
17+
if (expDiffRecStatements.length !== 0) {
18+
return {
19+
pass: false,
20+
message: () => `Expected graph does not equal to received graph\n
21+
Found ${receivedStatements.length} statements in received graph:
22+
${receivedStatements.map(st => `- ${st.subject} ${st.predicate} ${st.object} ${st.why} .\n`).join('')}
23+
The following ${expDiffRecStatements.length} statements does not exist in the above graph:
24+
${expDiffRecStatements.map(st => `- ${st.subject} ${st.predicate} ${st.object} ${st.why} .\n`).join('')}`
25+
}
26+
}
27+
const recDiffExpStatements = receivedStatements.filter(st => !expected.holds(st.subject, st.predicate, st.object, st.why as NamedNode))
28+
return {
29+
pass: recDiffExpStatements.length === 0,
30+
message: () => `Expected graph does not equal to received graph\n
31+
Found ${expectedStatements.length} statements in expected graph:
32+
${expectedStatements.map(st => `- ${st.subject} ${st.predicate} ${st.object} ${st.why} .\n`).join('')}
33+
The following ${recDiffExpStatements.length} statements does not exist in the above graph:
34+
${recDiffExpStatements.map(st => `- ${st.subject} ${st.predicate} ${st.object} ${st.why} .\n`).join('')}`
35+
}
36+
}
37+
38+
declare global {
39+
namespace jest {
40+
interface Matchers<R> {
41+
toEqualGraph (a: IndexedFormula): R;
42+
}
43+
}
44+
}

test/setup.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import { log, warn, error, trace } from '../src/debug'
1+
import { toContainGraph } from './custom-matchers/toContainGraph'
2+
import { toEqualGraph } from './custom-matchers/toEqualGraph'
23

34
// We don't want to output debug messages to console as part of the tests
45
jest.mock('../src/debug')
6+
const debug = require('../src/debug')
7+
debug.log.mockImplementation(() => null)
8+
debug.warn.mockImplementation(() => null)
9+
debug.error.mockImplementation(() => null)
10+
debug.trace.mockImplementation(() => null)
511

6-
export function silenceDebugMessages () {
7-
;(log as any).mockImplementation(() => null)
8-
;(warn as any).mockImplementation(() => null)
9-
;(error as any).mockImplementation(() => null)
10-
;(trace as any).mockImplementation(() => null)
11-
}
12+
// adding custom matchers
13+
expect.extend({
14+
toContainGraph,
15+
toEqualGraph
16+
})

0 commit comments

Comments
 (0)