Skip to content
This repository has been archived by the owner on Jul 25, 2023. It is now read-only.

Commit

Permalink
feat: Tests + first draft
Browse files Browse the repository at this point in the history
  • Loading branch information
cristianvasquez committed Mar 20, 2023
1 parent cdc233f commit 648e2e8
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 33 deletions.
32 changes: 22 additions & 10 deletions lib/appendGitlabProv.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import rdf from 'rdf-ext'
import { Transform } from 'readable-stream'
import { provFromGitlab } from './metadata/produceProv.js'
import { checkGitlabVars, provFromGitlab } from './metadata/produceProv.js'
import { prov } from './namespaces.js'

const typePredicate = rdf.namedNode(
Expand All @@ -9,14 +9,20 @@ const typePredicate = rdf.namedNode(
class ProvMetadata extends Transform {
constructor (context, { subjectsWithClass, graph }) {
super({ objectMode: true })

this.type = subjectsWithClass
this.provPointer = provFromGitlab()
const { omitProv, message } = checkGitlabVars()
if (omitProv) {
console.warn(message)
this.omitProv = omitProv
} else {
this.provPointer = provFromGitlab()
}
this.graph = graph
}

_transform (quad, encoding, callback) {
if (quad.predicate.equals(typePredicate) && quad.object.equals(this.type)) {
if (quad.predicate.equals(typePredicate) && quad.object.equals(this.type) &&
!this.omitProv) {
this.provPointer.addOut(prov.generates, quad.subject)
}

Expand All @@ -25,12 +31,14 @@ class ProvMetadata extends Transform {

async _flush (callback) {
try {
for (const quad of [...this.provPointer.dataset]) {
if (this.graph) {
this.push(
rdf.quad(quad.subject, quad.predicate, quad.object, this.graph))
} else {
this.push(quad)
if (!this.omitProv) {
for (const quad of [...this.provPointer.dataset]) {
if (this.graph) {
this.push(
rdf.quad(quad.subject, quad.predicate, quad.object, this.graph))
} else {
this.push(quad)
}
}
}
} catch (err) {
Expand All @@ -51,6 +59,10 @@ function toNamedNode (item) {
async function appendGitlabProv ({
subjectsWithClass, graph = undefined
} = {}) {
if (!subjectsWithClass) {
throw new Error('Needs subjectsWithClass as parameter (string or namedNode)')
}

return new ProvMetadata(this, {
subjectsWithClass: toNamedNode(subjectsWithClass),
graph: toNamedNode(graph)
Expand Down
41 changes: 18 additions & 23 deletions lib/metadata/produceProv.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import namespace from '@rdfjs/namespace'
import clownface from 'clownface'
import rdf from 'rdf-ext'
import { xsd, schema, prov } from '../namespaces.js'

Expand All @@ -10,30 +11,24 @@ const ex = namespace('http://example.org/')

const type = rdf.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type')

function validateVars () {
if (!process.env.CI_JOB_URL) {
throw Error('required environment variable CI_JOB_URL')
}
if (!process.env.CI_JOB_STARTED_AT) {
throw Error('required environment variable CI_JOB_STARTED_AT')
}
if (!process.env.CI_PROJECT_URL) {
throw Error('required environment variable CI_PROJECT_URL')
}
if (!process.env.CI_COMMIT_SHA) {
throw Error('required environment variable CI_COMMIT_SHA')
}
if (!process.env.CI_PIPELINE_URL) {
throw Error('required environment variable CI_PIPELINE_URL')
}
if (!process.env.CI_PIPELINE_CREATED_AT) {
throw Error('required environment variable CI_PIPELINE_CREATED_AT')
}
const requiredVars = [
'CI_JOB_URL',
'CI_JOB_STARTED_AT',
'CI_PROJECT_URL',
'CI_COMMIT_SHA',
'CI_PIPELINE_URL',
'CI_PIPELINE_CREATED_AT']

function checkGitlabVars () {
const notFound = requiredVars.filter(varName => !process.env[varName])
const omitProv = notFound.length > 0
const message = omitProv
? `some of the required environment variables required to generate PROV metadata were not found [${notFound}]`
: undefined
return { omitProv, message }
}

function provFromGitlab () {
validateVars()

// Job
const jobUrl = process.env.CI_JOB_URL
const jobStartTime = process.env.CI_JOB_STARTED_AT
Expand All @@ -55,7 +50,7 @@ function provFromGitlab () {
// all the pipelines for the codebase
const pipelinesUri = rdf.namedNode(withoutLastSegment(pipelineRun))

const pointer = rdf.clownface({ dataset: rdf.dataset(), term: jobUri })
const pointer = clownface({ dataset: rdf.dataset(), term: jobUri })

pointer.node(codebaseUri)
.addOut(type, ex.Codebase)
Expand Down Expand Up @@ -115,4 +110,4 @@ function provFromGitlab () {
return pointer
}

export { provFromGitlab }
export { checkGitlabVars, provFromGitlab }
103 changes: 103 additions & 0 deletions test/appendGitlabProv.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { strictEqual } from 'assert'
import namespace from '@rdfjs/namespace'
import assertThrows from 'assert-throws-async'
import getStream from 'get-stream'
import { isDuplex } from 'isstream'
import { describe, it } from 'mocha'
import rdf from 'rdf-ext'
import { Readable } from 'readable-stream'
import appendGitlabProv from '../lib/appendGitlabProv.js'
import { dcat } from '../lib/namespaces.js'
import {
clearMockEnvironment, setMockEnvironment
} from './support/gitlabEnvironment.js'

const ex = namespace('http://example.org/')
const typePredicate = rdf.namedNode(
'http://www.w3.org/1999/02/22-rdf-syntax-ns#type')

describe('metadata.appendGitlabProv', () => {
it('should be a factory', () => {
strictEqual(typeof appendGitlabProv, 'function')
})

it('should throw an error if no argument is given', async () => {
await assertThrows(async () => {
await appendGitlabProv()
}, Error, /Needs subjectsWithClass as parameter/)
})

it(
'should return a duplex stream with a subjectsWithClass (namedNode) metadata parameter',
async () => {
const step = await appendGitlabProv({
subjectsWithClass: dcat.Dataset
})
strictEqual(isDuplex(step), true)
})

it(
'should return a duplex stream with a subjectsWithClass (string) metadata parameter',
async () => {
const step = await appendGitlabProv({
subjectsWithClass: dcat.Dataset
})
strictEqual(isDuplex(step), true)
})

it('should append no prov metadata with no environment variables',
async () => {
const initial = [
rdf.quad(ex.subject0, typePredicate, dcat.Dataset, ex.graph0)]

const step = await appendGitlabProv({
subjectsWithClass: dcat.Dataset
})

const result = await getStream.array(Readable.from(initial).pipe(step))

strictEqual(result.length, 1)
strictEqual(result[0].equals(initial[0]), true)
})

it('should append prov metadata with environment variables', async () => {
setMockEnvironment()

const initial = [
rdf.quad(ex.subject0, typePredicate, dcat.Dataset, ex.graph0)]

const step = await appendGitlabProv({
subjectsWithClass: dcat.Dataset
})

const result = await getStream.array(Readable.from(initial).pipe(step))

strictEqual(result.length > 1, true)
clearMockEnvironment()
})

it('should append prov metadata with the specified graph', async () => {
setMockEnvironment()

const initial = [
rdf.quad(ex.subject0, typePredicate, dcat.Dataset, ex.graph0)]

const step = await appendGitlabProv({
subjectsWithClass: dcat.Dataset, graph: ex.graph1
})

const result = await getStream.array(Readable.from(initial).pipe(step))

strictEqual(result.length > 1, true)

for (const [index, quad] of result.entries()) {
if (index === 0) {
strictEqual(quad.graph.equals(ex.graph0), true)
} else {
strictEqual(quad.graph.equals(ex.graph1), true)
}
}

clearMockEnvironment()
})
})
60 changes: 60 additions & 0 deletions test/metadata/produceProv.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { strictEqual } from 'assert'
import { describe, it } from 'mocha'
import {
checkGitlabVars, provFromGitlab
} from '../../lib/metadata/produceProv.js'
import {
clearMockEnvironment, setMockEnvironment
} from '../support/gitlabEnvironment.js'

describe('checkGitlabVars', () => {
it('should be a function', () => {
strictEqual(typeof checkGitlabVars, 'function')
})

it('omitProv is true if a mandatory variable is not set', async () => {
clearMockEnvironment()
const { omitProv, message } = checkGitlabVars()
strictEqual(omitProv, true)
strictEqual(message.length > 0, true)
})

it('omitProv is false if a mandatory variable is set', async () => {
setMockEnvironment()
const { omitProv, message } = checkGitlabVars()
strictEqual(omitProv, false)
strictEqual(message, undefined)
clearMockEnvironment()
})
})

const snapshot = `<https://example.org/user/pipeline> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Codebase> .
<https://example.org/user/pipeline> <http://example.org/hasPipelines> <https://example.org/user/pipeline/-/pipelines> .
<https://example.org/user/pipeline> <http://schema.org/description> "Test pipeline" .
<https://example.org/user/pipeline> <http://schema.org/name> "test-pipeline" .
<https://example.org/user/pipeline/-/pipelines> <http://example.org/contains> <https://example.org/user/pipeline/-/pipelines/36212> .
<https://example.org/user/pipeline/-/commit/30dd92dc282586159c8d4401d26262351f7228e0> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Commit> .
<https://example.org/user/pipeline/-/commit/30dd92dc282586159c8d4401d26262351f7228e0> <http://example.org/triggered> <https://example.org/user/pipeline/-/pipelines/36212> .
<https://example.org/user/pipeline/-/commit/30dd92dc282586159c8d4401d26262351f7228e0> <http://example.org/author> "User <[email protected]>" .
<https://example.org/user/pipeline/-/commit/30dd92dc282586159c8d4401d26262351f7228e0> <http://www.w3.org/ns/prov#atTime> "2023-03-14T12:24:50+01:00"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
<https://example.org/user/pipeline/-/pipelines/36212> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/PipelineRun> .
<https://example.org/user/pipeline/-/pipelines/36212> <http://www.w3.org/ns/prov#startedAtTime> "2023-03-14T12:24:53+01:00"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
<https://example.org/user/pipeline/-/pipelines/36212> <http://example.org/hasJob> <https://example.org/user/pipeline/-/jobs/48940> .
<https://example.org/user/pipeline/-/jobs/48940> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Activity> .
<https://example.org/user/pipeline/-/jobs/48940> <http://www.w3.org/ns/prov#startedAtTime> "2023-03-14T12:25:09+01:00"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
<https://example.org/user/pipeline/-/jobs/48940> <http://www.w3.org/ns/prov#wasTriggeredBy> <https://example.org/user/pipeline/-/commit/30dd92dc282586159c8d4401d26262351f7228e0> .
<https://example.org/user/pipeline/-/jobs/48940> <http://example.org/hasEnvironment> <http://example.org/environment/develop> .
`

describe('provFromGitlab', () => {
it('should be a function', () => {
strictEqual(typeof provFromGitlab, 'function')
})

it('provFromGitlab produces a provenance template', async () => {
setMockEnvironment()
const pointer = provFromGitlab()
strictEqual(pointer.dataset.toString(), snapshot)
clearMockEnvironment()
})
})
38 changes: 38 additions & 0 deletions test/support/gitlabEnvironment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const mockEnvironment = {
CI_PROJECT_ID: 123,
CI_PROJECT_URL: 'https://example.org/user/pipeline',
CI_PROJECT_DESCRIPTION: 'Test pipeline',
CI_PROJECT_NAMESPACE: 'user',
CI_PROJECT_NAME: 'test-pipeline',

GITLAB_USER_EMAIL: '[email protected]',
GITLAB_USER_LOGIN: 'user',

CI_COMMIT_AUTHOR: 'User <[email protected]>',
CI_COMMIT_TIMESTAMP: '2023-03-14T12:24:50+01:00',
CI_COMMIT_SHA: '30dd92dc282586159c8d4401d26262351f7228e0',
CI_PIPELINE_URL: 'https://example.org/user/pipeline/-/pipelines/36212',

CI_JOB_URL: 'https://example.org/user/pipeline/-/jobs/48940',
CI_JOB_STARTED_AT: '2023-03-14T12:25:09+01:00',
CI_BUILD_REF_SLUG: 'develop',

CI_PIPELINE_ID: 36212,
CI_PIPELINE_IID: 6,
CI_PIPELINE_CREATED_AT: '2023-03-14T12:24:53+01:00',
CI_JOB_IMAGE: 'pipeline/node-java-jena:latest'
}

function setMockEnvironment (vars) {
for (const [key, value] of Object.entries(mockEnvironment)) {
process.env[key] = `${value}`
}
}

function clearMockEnvironment (vars) {
for (const [key] of Object.entries(mockEnvironment)) {
delete process.env[key]
}
}

export { setMockEnvironment, clearMockEnvironment }

0 comments on commit 648e2e8

Please sign in to comment.