Skip to content

Commit

Permalink
Add tests to @soid/koa, and fix bug that tests uncovered
Browse files Browse the repository at this point in the history
Also run github workflows for lint, test, and build
Build only checks that the packages are actually building
  • Loading branch information
mrkvon committed Nov 1, 2024
1 parent 503221a commit 4f30d53
Show file tree
Hide file tree
Showing 13 changed files with 6,159 additions and 131 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Build

on: push

jobs:
build:
name: Build
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install NPM packages
run: yarn install --frozen-lockfile

- name: Build the packages
run: yarn lerna run build
26 changes: 26 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Lint

on: push

jobs:
lint:
name: Lint
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install NPM packages
run: yarn install --frozen-lockfile

- name: Run linter
run: yarn lint

- name: Run prettier check
run: yarn prettier --check .
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Test

on: push

jobs:
test:
name: Test
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install NPM packages
run: yarn install --frozen-lockfile

- name: Run tests
run: yarn lerna run test
4 changes: 2 additions & 2 deletions packages/core/src/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export const getAuthenticatedFetch = async (
const now = Math.floor(Date.now() / 1000)

const token = await new SignJWT({
webid: `${baseUrl}/profile/card#bot`,
sub: `${baseUrl}/profile/card#bot`, // Bot's WebID
webid: webId,
sub: webId, // Bot's WebID
cnf: { jkt },
})
.setProtectedHeader({ alg: 'ES256', typ: 'at+jwt', kid })
Expand Down
9 changes: 7 additions & 2 deletions packages/koa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,19 @@
"build:cjs:package": "cp ../../commonjspkg.json ./dist/cjs/package.json",
"build": "rm -rf dist && yarn build:esm && yarn build:cjs",
"prepublishOnly": "yarn build",
"test": "",
"test": "NODE_ENV=vitest vitest",
"lint": "",
"format": ""
},
"devDependencies": {
"@koa/router": "^13.1.0",
"@solid/community-server": "7.0.4",
"@types/koa__router": "^12.0.4",
"typescript": "^5.6.2"
"css-authn": "^0.0.16",
"koa": "^2.15.3",
"rdf-namespaces": "^1.12.0",
"typescript": "^5.6.2",
"vitest": "^2.1.4"
},
"dependencies": {
"@soid/core": "workspace:^"
Expand Down
7 changes: 7 additions & 0 deletions packages/koa/src/tests/css-pod-seed.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"email": "person@example",
"password": "password",
"pods": [{ "name": "person" }]
}
]
87 changes: 87 additions & 0 deletions packages/koa/src/tests/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { parseLinkHeader } from '@solid/community-server'
import { createAccount, getAuthenticatedFetch } from 'css-authn/dist/7.x.js'
import { randomUUID } from 'node:crypto'
import { expect } from 'vitest'
import { Person } from './types.js'

export const createRandomAccount = async ({
solidServer,
}: {
solidServer: string
}) => {
const account = await createAccount({
username: randomUUID(),
password: randomUUID(),
email: randomUUID() + '@example.com',
provider: solidServer,
})

const authenticatedFetch = await getAuthenticatedFetch({
email: account.email,
password: account.password,
provider: solidServer,
})

return { ...account, fetch: authenticatedFetch }
}

/**
* Find link to ACL document for a given URI
*/
export const getAcl = async (
uri: string,
ffetch: typeof globalThis.fetch = globalThis.fetch,
) => {
const response = await ffetch(uri, { method: 'HEAD' })
expect(response.ok).toEqual(true)
const linkHeader = response.headers.get('link')
const links = parseLinkHeader(linkHeader ?? '')
const aclLink = links.find(link => link.parameters.rel === 'acl')
const aclUri = aclLink?.target
if (!aclUri) throw new Error(`We could not find WAC link for ${uri}`)
// if aclUri is relative, return absolute uri
return new URL(aclUri, uri).toString()
}

export const getContainer = (uri: string) =>
uri.substring(0, uri.lastIndexOf('/') + 1)

export const getResource = (uri: string) => {
const url = new URL(uri)
const clearedUrl = new URL(url.pathname, url.origin).toString()
return clearedUrl
}

export const getDefaultPerson = async (
{
email,
password,
pods: [{ name }],
}: {
email: string
password: string
pods: [{ name: string }]
},
cssUrl: string,
): Promise<Person> => {
const podUrl = `${cssUrl}/${name}/`
const withoutFetch: Omit<Person, 'fetch'> = {
podUrl,
idp: cssUrl + '/',
webId: podUrl + 'profile/card#me',
username: name,
password,
email,
}
return {
...withoutFetch,
fetch: await getAuthenticatedFetch({ ...withoutFetch, provider: cssUrl }),
}
}

export function getRandomPort(): number {
// Generate a random number between 1024 and 65535
const min = 1024
const max = 65535
return Math.floor(Math.random() * (max - min + 1)) + min
}
158 changes: 158 additions & 0 deletions packages/koa/src/tests/helpers/setupPod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { foaf, solid } from 'rdf-namespaces'
import { expect } from 'vitest'
import { getAcl, getContainer, getResource } from './index'

interface ACLConfig {
permissions: ('Read' | 'Write' | 'Append' | 'Control')[]
agents?: string[]
agentGroups?: string[]
agentClasses?: string[]
isDefault?: boolean
}

export const createContainer = async ({
url,
acls,
authenticatedFetch,
}: {
url: string
acls?: ACLConfig[]
authenticatedFetch: typeof fetch
}) => {
const response = await authenticatedFetch(getContainer(url), {
method: 'PUT',
headers: {
'content-type': 'text/turtle',
Link: '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"',
},
})

expect(response.ok).toEqual(true)

if (acls) {
for (const aclConfig of acls) {
await addAcl({
...aclConfig,
resource: url,
authenticatedFetch,
})
}
}
}

export const createResource = async ({
url,
body,
acls,
authenticatedFetch,
}: {
url: string
body: string
acls?: ACLConfig[]
authenticatedFetch: typeof fetch
}) => {
const response = await authenticatedFetch(getResource(url), {
method: 'PUT',
headers: { 'content-type': 'text/turtle' },
body,
})

expect(response.ok).toEqual(true)

if (acls) {
for (const aclConfig of acls) {
await addAcl({
...aclConfig,
resource: getResource(url),
authenticatedFetch,
})
}
}
}

export const patchFile = async ({
url,
inserts = '',
deletes = '',
authenticatedFetch,
}: {
url: string
inserts?: string
deletes?: string
authenticatedFetch: typeof fetch
}) => {
if (!inserts && !deletes) return
const patch = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
_:patch a solid:InsertDeletePatch;
${inserts ? `solid:inserts { ${inserts} }` : ''}
${inserts && deletes ? ';' : ''}
${deletes ? `solid:deletes { ${deletes} }` : ''}
.`
const response = await authenticatedFetch(url, {
method: 'PATCH',
body: patch,
headers: { 'content-type': 'text/n3' },
})
expect(response.ok).toEqual(true)
}

const addAcl = async ({
permissions,
agents,
agentGroups,
agentClasses,
isPublic = false,
resource,
isDefault = false,
authenticatedFetch,
}: {
permissions: ('Read' | 'Write' | 'Append' | 'Control')[]
agents?: string[]
agentGroups?: string[]
agentClasses?: string[]
isPublic?: boolean
resource: string
isDefault?: boolean
authenticatedFetch: typeof globalThis.fetch
}) => {
if (permissions.length === 0)
throw new Error('You need to specify at least one permission')

const acl = await getAcl(resource, authenticatedFetch)

const response = await authenticatedFetch(acl, {
method: 'PATCH',
headers: { 'content-type': 'text/n3' },
body: `
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
_:mutate a <${solid.InsertDeletePatch}>; <${solid.inserts}> {
<#${permissions.join('')}>
a acl:Authorization;
${
agents && agents.length > 0
? `acl:agent ${agents.map(a => `<${a}>`).join(', ')};`
: ''
}
${
agentGroups && agentGroups.length > 0
? `acl:agentGroup ${agentGroups.map(a => `<${a}>`).join(', ')};`
: ''
}
${
agentClasses && agentClasses.length > 0
? `acl:agentClass ${agentClasses.map(a => `<${a}>`).join(', ')};`
: ''
}
${isPublic ? `acl:agentClass <${foaf.Agent}>;` : ''}
acl:accessTo <${resource}>;
${isDefault ? `acl:default <${resource}>;` : ''}
acl:mode ${permissions.map(p => `acl:${p}`).join(', ')}.
}.`,
})

expect(response.ok).toEqual(true)

return response
}
9 changes: 9 additions & 0 deletions packages/koa/src/tests/helpers/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface Person {
idp: string
podUrl: string
webId: string
username: string
password: string
email: string
fetch: typeof globalThis.fetch
}
Loading

0 comments on commit 4f30d53

Please sign in to comment.