Skip to content

Commit

Permalink
Add tests to @soid/koa, and fix bug that tests uncovered (#2)
Browse files Browse the repository at this point in the history
Also run github workflows for lint and test
  • Loading branch information
mrkvon authored Nov 1, 2024
1 parent 503221a commit 1519427
Show file tree
Hide file tree
Showing 13 changed files with 6,148 additions and 131 deletions.
19 changes: 19 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Setup environment
description: 'Set up the repository to run yarn commands on it etc...'

runs:
using: 'composite'

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

- name: Enable corepack for yarn berry
shell: bash
run: corepack enable

- name: Install NPM packages
shell: bash
run: yarn install --immutable
21 changes: 21 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Lint

on: push

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

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

- name: Setup
uses: './.github/actions/setup'

- name: Lint
run: yarn lint

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

on: push

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

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

- name: Setup
uses: './.github/actions/setup'

- name: Build packages
run: yarn lerna run build

- name: Run tests
run: yarn workspaces foreach --all run test run
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 1519427

Please sign in to comment.