Skip to content

Commit fc4ed7b

Browse files
authored
Merge pull request SolidOS#283 from solid/acl-cleanup
Acl cleanup
2 parents cfb738b + f69af75 commit fc4ed7b

20 files changed

Lines changed: 598 additions & 120 deletions
Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
# Testing Documentation and Guidelines
22

3+
## Custom matchers
4+
5+
We have added some custom matchers to ease the testing. You can see the full list at `test/setup.ts`, in
6+
the `expect.extend` part.
7+
8+
* `expect(A).toEqualGraph(B)`: Use this matcher to check whether graphs A and B are equal (meaning containing the
9+
same set of triples)
10+
* `expect(A).toContainGraph(B)`: Use this matcher to check whether graph B is contained in graph A
11+
312
## Notes
413

5-
The original code was not written with testing in mind. In order to make testing more efficient you may find it easier to export a function. To do this include the following comment above the function so that it does not get picked up by Typedoc.
14+
The original code was not written with testing in mind. In order to make testing more efficient you may find it
15+
easier to export a function. To do this include the following comment above the function so that it does not
16+
get picked up by Typedoc.
617
`@ignore exporting this only for the unit test`
718

8-
There will also be times that even exporting the function isn't enough to enable proper tests to be developed. In this case follow the commenting procedures in the Code Readme.md, which is to add the comment
19+
There will also be times that even exporting the function isn't enough to enable proper tests to be developed.
20+
In this case follow the commenting procedures in the Code Readme.md, which is to add the comment
921
` \* @@ TODO and desribe the problem.
1022

11-
You can reference https://github.com/solid/solid-ui/issues/215 in your TODO comment if the code is hard to test due to DOM manipulation.
23+
You can reference https://github.com/solid/solid-ui/issues/215 in your TODO comment if the code is hard to
24+
test due to DOM manipulation.

__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 = {}

jest.config.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
module.exports = {
2-
verbose: true
2+
verbose: true,
3+
setupFilesAfterEnv: [
4+
'./test/setup.ts'
5+
]
36
}

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,7 +128,7 @@ export function sameACL (a: AgentMapMap, b: AgentMapMap): boolean {
139128
/**
140129
* Union N ACLs
141130
*/
142-
export function ACLunion (list: Array<AgentMapMap>): AgentMapMap {
131+
export function ACLunion (list: Array<AgentMapMap | AgentMapUnion>): AgentMapUnion {
143132
const b = list[0]
144133
let a, ag
145134
for (let k = 1; k < list.length; k++) {
@@ -157,13 +146,13 @@ export function ACLunion (list: Array<AgentMapMap>): AgentMapMap {
157146
}
158147
)
159148
}
160-
return b
149+
return b as AgentMapUnion
161150
}
162151

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: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { log, warn, error, trace } from '../src/debug'
1+
import { toContainGraph } from './custom-matchers/toContainGraph'
2+
import { toEqualGraph } from './custom-matchers/toEqualGraph'
3+
import { error, log, trace, warn } from '../src/debug'
24

35
// We don't want to output debug messages to console as part of the tests
46
jest.mock('../src/debug')
@@ -9,3 +11,9 @@ export function silenceDebugMessages () {
911
;(error as any).mockImplementation(() => null)
1012
;(trace as any).mockImplementation(() => null)
1113
}
14+
15+
// adding custom matchers
16+
expect.extend({
17+
toContainGraph,
18+
toEqualGraph
19+
})

0 commit comments

Comments
 (0)