Skip to content

Commit

Permalink
test: negative cases
Browse files Browse the repository at this point in the history
  • Loading branch information
tpluscode committed Aug 9, 2024
1 parent d847c34 commit a97d777
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 96 deletions.
11 changes: 6 additions & 5 deletions packages/core/lib/resourceShape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ import type { Kopflos, KopflosResponse } from './Kopflos.js'
interface ResourceShapeDirectMatch {
api: NamedNode
resourceShape: NamedNode
subject: NamedNode
}

interface ResourceShapeTypeMatch {
api: NamedNode
resourceShape: NamedNode
subject: NamedNode
}

interface ResourceShapeObjectMatch {
api: NamedNode
resourceShape: NamedNode
property: NamedNode
subject: NamedNode
object: NamedNode
}

export type ResourceShapeMatch = ResourceShapeDirectMatch | ResourceShapeTypeMatch | ResourceShapeObjectMatch
Expand All @@ -30,8 +32,7 @@ const __dirname = path.dirname(new URL(import.meta.url).pathname)
const select = fs.readFileSync(path.resolve(__dirname, '../query/resourceShapes.rq')).toString()

export default async (iri: NamedNode, instance: Kopflos) => {
return instance.env.sparql.default.parsed.query.select(`
${select}
VALUES ?resource { <${iri.value}> }
`) as unknown as Promise<ResourceShapeMatch[]>
return instance.env.sparql.default.parsed.query.select(
select.replace('sh:this', `<${iri.value}>`),
) as unknown as Promise<ResourceShapeMatch[]>
}
35 changes: 21 additions & 14 deletions packages/core/query/resourceShapes.rq
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,30 @@ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX sh: <http://www.w3.org/ns/shacl#>
prefix kopflos: <https://kopflos.described.at/>

SELECT distinct ?api ?resource ?parent ?resourceShape ?property {
SELECT * {
{
?resourceShape a kopflos:ResourceShape .
?resourceShape kopflos:api ?api .
SELECT ?api ?resourceShape ?subject ?property ?object {
{
?resourceShape a kopflos:ResourceShape .
?resourceShape kopflos:api ?api .

{
?resourceShape sh:targetClass ?targetClass .
?type rdfs:subClassOf* ?targetClass .
?subject a ?type .
} UNION {
?resourceShape sh:targetNode ?subject .
}
}

optional {
?resourceShape sh:property/sh:path ?property .
?subject ?property ?object .
}

{
?resourceShape sh:targetClass ?targetClass .
?type rdfs:subClassOf* ?targetClass .
?candidate a ?type .
} UNION {
?resourceShape sh:targetNode ?candidate .
}
}

OPTIONAL {
?resourceShape sh:property/sh:path ?property .
?candidate ?property ?resource .
BIND(?candidate as ?parent)
}
VALUES ?resource { sh:this }
FILTER(?resource = ?subject || ?resource = ?object)
}
3 changes: 3 additions & 0 deletions packages/core/test/lib/Kopflos.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ describe('lib/Kopflos', () => {
resourceShapeLookup: async () => [{
api: ex.api,
resourceShape: ex.Shape,
subject: ex.foo,
}],
resourceLoaderLookup: async () => undefined,
})
Expand All @@ -66,6 +67,7 @@ describe('lib/Kopflos', () => {
resourceShapeLookup: async () => [{
api: ex.api,
resourceShape: ex.FooShape,
subject: ex.foo,
}],
handlerLookup: async () => undefined,
})
Expand All @@ -86,6 +88,7 @@ describe('lib/Kopflos', () => {
resourceShapeLookup: async () => [{
api: ex.api,
resourceShape: ex.FooShape,
subject: ex.foo,
}],
handlerLookup: async () => async () => ({
status: 200,
Expand Down
153 changes: 94 additions & 59 deletions packages/core/test/lib/resourceShape.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,83 +20,118 @@ describe('lib/resourceShape', () => {
})

describe('default resource shape lookup', () => {
it('finds directly matching resource', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)

// when
const results = await defaultResourceShapeLookup(ex.bar, kopflos)

// then
expect(results[0]).to.deep.contain({
api: ex.api,
resourceShape: ex.barShape,
context('when directly matching resource shape is available', () => {
it('is found by resource IRI', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)

// when
const results = await defaultResourceShapeLookup(ex.bar, kopflos)

// then
expect(results[0]).to.deep.contain({
api: ex.api,
resourceShape: ex.barShape,
subject: ex.bar,
})
expect(results).to.have.length(1)
})
expect(results).to.have.length(1)
})

it('finds matching resource by type', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)
it('does not find anything when requested resource does not match that shape', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)

// when
const results = await defaultResourceShapeLookup(ex.foo, kopflos)
// when
const results = await defaultResourceShapeLookup(ex.boo, kopflos)

// then
expect(results[0]).to.deep.contain({
api: ex.api,
resourceShape: ex.FooShape,
// then
expect(results).to.be.empty
})
expect(results).to.have.length(1)
})

it('finds matching resource by super type', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)
context('when class targeting resource shape is available', () => {
it('is found when requested resource has that type', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)

// when
const results = await defaultResourceShapeLookup(ex.foo, kopflos)

// then
expect(results[0]).to.deep.contain({
api: ex.api,
resourceShape: ex.FooShape,
subject: ex.foo,
})
expect(results).to.have.length(1)
})

it('is found when requested resource has derived type', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)

// when
const results = await defaultResourceShapeLookup(ex.baz, kopflos)
// when
const results = await defaultResourceShapeLookup(ex.baz, kopflos)

// then
expect(results[0]).to.deep.contain({
api: ex.api,
resourceShape: ex.FooShape,
// then
expect(results[0]).to.deep.contain({
api: ex.api,
resourceShape: ex.FooShape,
subject: ex.baz,
})
expect(results).to.have.length(1)
})
expect(results).to.have.length(1)
})

it('finds matching resource by property usage', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)
it('find nothing when a resource exists but has a different type', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)

// when
const results = await defaultResourceShapeLookup(ex['foo/location'], kopflos)
// when
const results = await defaultResourceShapeLookup(ex.xyz, kopflos)

// then
expect(results[0]).to.deep.contain({
api: ex.api,
resourceShape: ex.FooShape,
parent: ex.foo,
property: rdf.ns.schema.location,
// then
expect(results).to.be.empty
})
expect(results).to.have.length(1)
})

it('finds matching resource by property usage of resource shape with target node', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)

// when
const results = await defaultResourceShapeLookup(ex['foo/location'], kopflos)
context('when class targeting resource shapes has property shape', () => {
it('finds matching resource by property usage of class targeting shape', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)

// when
const results = await defaultResourceShapeLookup(ex['foo/location'], kopflos)

// then
expect(results[0]).to.deep.contain({
api: ex.api,
resourceShape: ex.FooShape,
subject: ex.foo,
object: ex['foo/location'],
property: rdf.ns.schema.location,
})
expect(results).to.have.length(1)
})
})

// then
expect(results[0]).to.deep.contain({
api: ex.api,
resourceShape: ex.FooShape,
parent: ex.foo,
property: rdf.ns.schema.location,
context('when node targeting resource shapes has property shape', () => {
it('finds matching resource by property usage of resource shape with target node', async () => {
// given
const kopflos = new Kopflos(rdf.clownface(), options)

// when
const results = await defaultResourceShapeLookup(ex['foo/location'], kopflos)

// then
expect(results[0]).to.deep.contain({
api: ex.api,
resourceShape: ex.FooShape,
subject: ex.foo,
object: ex['foo/location'],
property: rdf.ns.schema.location,
})
expect(results).to.have.length(1)
})
expect(results).to.have.length(1)
})
})
})
22 changes: 5 additions & 17 deletions packages/core/test/lib/resourceShape.test.ts.trig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX kopflos: <https://kopflos.described.at/>
PREFIX ex: <http://example.org/>

GRAPH <lib/resourceShape/default-resource-shape-lookup/finds-directly-matching-resource> {
GRAPH <default-resource-shape-lookup/when-directly-matching-resource-shape-is-available> {
# api
ex:api a kopflos:Api .

Expand All @@ -15,25 +15,13 @@ GRAPH <lib/resourceShape/default-resource-shape-lookup/finds-directly-matching-r
.
}

GRAPH <lib/resourceShape/default-resource-shape-lookup/finds-matching-resource-by-type> {
GRAPH <default-resource-shape-lookup/when-class-targeting-resource-shape-is-available> {
# resources
ex:foo a ex:Foo .

# api
ex:api a kopflos:Api .

ex:FooShape
a kopflos:ResourceShape ;
sh:targetClass ex:Foo ;
kopflos:api ex:api ;
.
}

GRAPH <lib/resourceShape/default-resource-shape-lookup/finds-matching-resource-by-super-type> {
# resources
ex:baz a ex:Baz .
ex:Baz rdfs:subClassOf ex:Bar .
ex:Bar rdfs:subClassOf ex:Foo .
ex:xyz a ex:Xyz .

# api
ex:api a kopflos:Api .
Expand All @@ -45,7 +33,7 @@ GRAPH <lib/resourceShape/default-resource-shape-lookup/finds-matching-resource-b
.
}

GRAPH <lib/resourceShape/default-resource-shape-lookup/finds-matching-resource-by-property-usage> {
GRAPH <default-resource-shape-lookup/when-class-targeting-resource-shapes-has-property-shape> {
# resources
ex:foo
a ex:Foo ;
Expand All @@ -63,7 +51,7 @@ GRAPH <lib/resourceShape/default-resource-shape-lookup/finds-matching-resource-b
.
}

GRAPH <lib/resourceShape/default-resource-shape-lookup/finds-matching-resource-by-property-usage-of-resource-shape-with-target-node> {
GRAPH <default-resource-shape-lookup/when-node-targeting-resource-shapes-has-property-shape> {
# resources
ex:foo
schema:location ex:foo\/location .
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/support/testData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function createStore(base: string, options: Options = {}) {
}

function testGraph(test: Mocha.Test): NamedNode {
return rdf.namedNode(encodeURI(test.titlePath().map(removeSpaces).join('/')))
return rdf.namedNode(encodeURI(test.titlePath().slice(1, -1).map(removeSpaces).join('/')))
}

function removeSpaces(arg: string) {
Expand Down

0 comments on commit a97d777

Please sign in to comment.