parent
8c258f3ede
commit
92a9a57f28
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,11 @@ |
||||
const sharedConfig = require('../../jest.config.js'); |
||||
const sharedConfig = require("../../jest.config.js"); |
||||
module.exports = { |
||||
...sharedConfig, |
||||
'rootDir': './', |
||||
} |
||||
preset: "ts-jest/presets/js-with-ts", |
||||
testEnvironment: "jsdom", |
||||
rootDir: "./", |
||||
transformIgnorePatterns: ["undici"], |
||||
injectGlobals: true, |
||||
testEnvironment: "<rootDir>/test/environment/customEnvironment.ts", |
||||
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"], |
||||
}; |
||||
|
@ -0,0 +1,2 @@ |
||||
import "@inrupt/jest-jsdom-polyfills"; |
||||
globalThis.fetch = async () => new Response(); |
@ -0,0 +1,41 @@ |
||||
import React from "react"; |
||||
import type { FunctionComponent } from "react"; |
||||
import { fireEvent, render, screen } from "@testing-library/react"; |
||||
import { setUpServer } from "./setUpServer"; |
||||
import { useSolidAuth } from "../src/SolidAuthContext"; |
||||
import { ROOT_CONTAINER } from "./solidServer.helper"; |
||||
import { BrowserSolidLdoProvider } from "../src/BrowserSolidLdoProvider"; |
||||
|
||||
describe("Browser Authentication", () => { |
||||
const s = setUpServer(); |
||||
|
||||
const AuthTest: FunctionComponent = () => { |
||||
const { login, session, logout } = useSolidAuth(); |
||||
|
||||
return ( |
||||
<div> |
||||
<div data-testid="session">{JSON.stringify(session)}</div> |
||||
<button onClick={() => login(ROOT_CONTAINER)} aria-label="login"> |
||||
Login |
||||
</button> |
||||
<button onClick={logout} aria-label="logout"> |
||||
LogOut |
||||
</button> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
it("properly logs in", async () => { |
||||
render( |
||||
<BrowserSolidLdoProvider> |
||||
<AuthTest /> |
||||
</BrowserSolidLdoProvider>, |
||||
); |
||||
const loginButton = screen.getByRole("button", { name: "logout" }); |
||||
fireEvent.click(loginButton); |
||||
await screen.findByText("Log in"); |
||||
expect(window.location.pathname).toBe("/.account/login/password/"); |
||||
// const authorizeButton = screen.getByText("Log in");
|
||||
// const emailBox = screen.getById
|
||||
}); |
||||
}); |
@ -0,0 +1,7 @@ |
||||
[ |
||||
{ |
||||
"podName": "example", |
||||
"email": "hello@example.com", |
||||
"password": "abc123" |
||||
} |
||||
] |
@ -0,0 +1,14 @@ |
||||
import Environment from "jest-environment-jsdom"; |
||||
|
||||
export default class CustomTestEnvironment extends Environment { |
||||
async setup() { |
||||
await super.setup(); |
||||
if (typeof this.global.TextEncoder === "undefined") { |
||||
// The following doesn't work from jest-jsdom-polyfills.
|
||||
// TextEncoder (global or via 'util') references a Uint8Array constructor
|
||||
// different than the global one used by users in tests. This makes sure the
|
||||
// same constructor is referenced by both.
|
||||
this.global.Uint8Array = Uint8Array; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,128 @@ |
||||
import type { ContainerUri, LeafUri } from "@ldo/solid"; |
||||
import { |
||||
ROOT_CONTAINER, |
||||
createApp, |
||||
getAuthenticatedFetch, |
||||
} from "./solidServer.helper"; |
||||
import type { App } from "@solid/community-server"; |
||||
|
||||
export const TEST_CONTAINER_SLUG = "test_ldo/"; |
||||
export const TEST_CONTAINER_URI = |
||||
`${ROOT_CONTAINER}${TEST_CONTAINER_SLUG}` as ContainerUri; |
||||
export const SAMPLE_DATA_URI = `${TEST_CONTAINER_URI}sample.ttl` as LeafUri; |
||||
export const SAMPLE2_DATA_SLUG = "sample2.ttl"; |
||||
export const SAMPLE2_DATA_URI = |
||||
`${TEST_CONTAINER_URI}${SAMPLE2_DATA_SLUG}` as LeafUri; |
||||
export const SAMPLE_BINARY_URI = `${TEST_CONTAINER_URI}sample.txt` as LeafUri; |
||||
export const SAMPLE2_BINARY_SLUG = `sample2.txt`; |
||||
export const SAMPLE2_BINARY_URI = |
||||
`${TEST_CONTAINER_URI}${SAMPLE2_BINARY_SLUG}` as LeafUri; |
||||
export const SAMPLE_CONTAINER_URI = |
||||
`${TEST_CONTAINER_URI}sample_container/` as ContainerUri; |
||||
export const SPIDER_MAN_TTL = `@base <http://example.org/> .
|
||||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . |
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . |
||||
@prefix foaf: <http://xmlns.com/foaf/0.1/> . |
||||
@prefix rel: <http://www.perceive.net/schemas/relationship/> . |
||||
|
||||
<#green-goblin> |
||||
rel:enemyOf <#spiderman> ; |
||||
a foaf:Person ; # in the context of the Marvel universe |
||||
foaf:name "Green Goblin" . |
||||
|
||||
<#spiderman> |
||||
rel:enemyOf <#green-goblin> ; |
||||
a foaf:Person ; |
||||
foaf:name "Spiderman", "Человек-паук"@ru .`;
|
||||
export const TEST_CONTAINER_TTL = `@prefix dc: <http://purl.org/dc/terms/>.
|
||||
@prefix ldp: <http://www.w3.org/ns/ldp#>. |
||||
@prefix posix: <http://www.w3.org/ns/posix/stat#>. |
||||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>. |
||||
|
||||
<> <urn:npm:solid:community-server:http:slug> "sample.txt"; |
||||
a ldp:Container, ldp:BasicContainer, ldp:Resource; |
||||
dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime. |
||||
<sample.ttl> a ldp:Resource, <http://www.w3.org/ns/iana/media-types/text/turtle#Resource>; |
||||
dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime. |
||||
<sample.txt> a ldp:Resource, <http://www.w3.org/ns/iana/media-types/text/plain#Resource>; |
||||
dc:modified "2023-10-20T13:57:14.000Z"^^xsd:dateTime. |
||||
<> posix:mtime 1697810234; |
||||
ldp:contains <sample.ttl>, <sample.txt>. |
||||
<sample.ttl> posix:mtime 1697810234; |
||||
posix:size 522. |
||||
<sample.txt> posix:mtime 1697810234; |
||||
posix:size 10.`;
|
||||
|
||||
export interface SetUpServerReturn { |
||||
app: App; |
||||
authFetch: typeof fetch; |
||||
fetchMock: jest.Mock< |
||||
Promise<Response>, |
||||
[input: RequestInfo | URL, init?: RequestInit | undefined] |
||||
>; |
||||
} |
||||
|
||||
export function setUpServer(): SetUpServerReturn { |
||||
// Ignore to build s
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const s: SetUpServerReturn = {}; |
||||
|
||||
beforeAll(async () => { |
||||
// Start up the server
|
||||
s.app = await createApp(); |
||||
await s.app.start(); |
||||
|
||||
s.authFetch = await getAuthenticatedFetch(); |
||||
}); |
||||
|
||||
afterAll(async () => { |
||||
s.app.stop(); |
||||
}); |
||||
|
||||
beforeEach(async () => { |
||||
s.fetchMock = jest.fn(s.authFetch); |
||||
// Create a new document called sample.ttl
|
||||
await s.authFetch(ROOT_CONTAINER, { |
||||
method: "POST", |
||||
headers: { |
||||
link: '<http://www.w3.org/ns/ldp#Container>; rel="type"', |
||||
slug: TEST_CONTAINER_SLUG, |
||||
}, |
||||
}); |
||||
await Promise.all([ |
||||
s.authFetch(TEST_CONTAINER_URI, { |
||||
method: "POST", |
||||
headers: { "content-type": "text/turtle", slug: "sample.ttl" }, |
||||
body: SPIDER_MAN_TTL, |
||||
}), |
||||
s.authFetch(TEST_CONTAINER_URI, { |
||||
method: "POST", |
||||
headers: { "content-type": "text/plain", slug: "sample.txt" }, |
||||
body: "some text.", |
||||
}), |
||||
]); |
||||
}); |
||||
|
||||
afterEach(async () => { |
||||
await Promise.all([ |
||||
s.authFetch(SAMPLE_DATA_URI, { |
||||
method: "DELETE", |
||||
}), |
||||
s.authFetch(SAMPLE2_DATA_URI, { |
||||
method: "DELETE", |
||||
}), |
||||
s.authFetch(SAMPLE_BINARY_URI, { |
||||
method: "DELETE", |
||||
}), |
||||
s.authFetch(SAMPLE2_BINARY_URI, { |
||||
method: "DELETE", |
||||
}), |
||||
s.authFetch(SAMPLE_CONTAINER_URI, { |
||||
method: "DELETE", |
||||
}), |
||||
]); |
||||
}); |
||||
|
||||
return s; |
||||
} |
@ -0,0 +1,122 @@ |
||||
// Taken from https://github.com/comunica/comunica/blob/b237be4265c353a62a876187d9e21e3bc05123a3/engines/query-sparql/test/QuerySparql-solid-test.ts#L9
|
||||
|
||||
import * as path from "path"; |
||||
import type { KeyPair } from "@inrupt/solid-client-authn-core"; |
||||
import { |
||||
buildAuthenticatedFetch, |
||||
createDpopHeader, |
||||
generateDpopKeyPair, |
||||
} from "@inrupt/solid-client-authn-core"; |
||||
import type { App } from "@solid/community-server"; |
||||
import { AppRunner, resolveModulePath } from "@solid/community-server"; |
||||
import "jest-rdf"; |
||||
import fetch from "cross-fetch"; |
||||
|
||||
const config = [ |
||||
{ |
||||
podName: process.env.USER_NAME || "example", |
||||
email: process.env.EMAIL || "hello@example.com", |
||||
password: process.env.PASSWORD || "abc123", |
||||
}, |
||||
]; |
||||
|
||||
export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/"; |
||||
export const ROOT_ROUTE = process.env.ROOT_CONTAINER || "example/"; |
||||
export const ROOT_CONTAINER = `${SERVER_DOMAIN}${ROOT_ROUTE}`; |
||||
|
||||
// Use an increased timeout, since the CSS server takes too much setup time.
|
||||
jest.setTimeout(40_000); |
||||
|
||||
export async function createApp(): Promise<App> { |
||||
if (process.env.SERVER) { |
||||
return { |
||||
start: () => {}, |
||||
stop: () => {}, |
||||
} as App; |
||||
} |
||||
const appRunner = new AppRunner(); |
||||
return appRunner.create( |
||||
{ |
||||
mainModulePath: resolveModulePath(""), |
||||
typeChecking: false, |
||||
}, |
||||
resolveModulePath("config/default.json"), |
||||
{}, |
||||
{ |
||||
port: 3_001, |
||||
loggingLevel: "off", |
||||
seededPodConfigJson: path.join( |
||||
__dirname, |
||||
"configs", |
||||
"solid-css-seed.json", |
||||
), |
||||
}, |
||||
); |
||||
} |
||||
|
||||
export interface ISecretData { |
||||
id: string; |
||||
secret: string; |
||||
} |
||||
|
||||
// From https://communitysolidserver.github.io/CommunitySolidServer/5.x/usage/client-credentials/
|
||||
export async function getSecret(): Promise<ISecretData> { |
||||
const result = await fetch(`${SERVER_DOMAIN}idp/credentials/`, { |
||||
method: "POST", |
||||
headers: { "content-type": "application/json" }, |
||||
body: JSON.stringify({ |
||||
email: config[0].email, |
||||
password: config[0].password, |
||||
name: config[0].podName, |
||||
}), |
||||
}); |
||||
const json = await result.json(); |
||||
return json; |
||||
} |
||||
|
||||
export interface ITokenData { |
||||
accessToken: string; |
||||
dpopKey: KeyPair; |
||||
} |
||||
|
||||
// From https://communitysolidserver.github.io/CommunitySolidServer/5.x/usage/client-credentials/
|
||||
export async function refreshToken({ |
||||
id, |
||||
secret, |
||||
}: ISecretData): Promise<ITokenData> { |
||||
const dpopKey = await generateDpopKeyPair(); |
||||
const authString = `${encodeURIComponent(id)}:${encodeURIComponent(secret)}`; |
||||
const tokenUrl = `${SERVER_DOMAIN}.oidc/token`; |
||||
const accessToken = await fetch(tokenUrl, { |
||||
method: "POST", |
||||
headers: { |
||||
// The header needs to be in base64 encoding.
|
||||
authorization: `Basic ${Buffer.from(authString).toString("base64")}`, |
||||
"content-type": "application/x-www-form-urlencoded", |
||||
dpop: await createDpopHeader(tokenUrl, "POST", dpopKey), |
||||
}, |
||||
body: "grant_type=client_credentials&scope=webid", |
||||
}) |
||||
.then((res) => res.json()) |
||||
.then((res) => res.access_token); |
||||
|
||||
return { accessToken, dpopKey }; |
||||
} |
||||
|
||||
export async function getAuthenticatedFetch() { |
||||
// Generate secret
|
||||
const secret = await getSecret(); |
||||
|
||||
if (!secret) throw new Error("No Secret"); |
||||
|
||||
// Get token
|
||||
const token = await refreshToken(secret); |
||||
|
||||
if (!token) throw new Error("No Token"); |
||||
|
||||
// Build authenticated fetch
|
||||
const authFetch = await buildAuthenticatedFetch(fetch, token.accessToken, { |
||||
dpopKey: token.dpopKey, |
||||
}); |
||||
return authFetch; |
||||
} |
@ -1,5 +0,0 @@ |
||||
describe("Trivial", () => { |
||||
it("Trivial", () => { |
||||
expect(true).toBe(true); |
||||
}); |
||||
}); |
@ -1,60 +0,0 @@ |
||||
{ |
||||
"name": "@ldo/solid", |
||||
"version": "0.0.1-alpha.17", |
||||
"description": "A library for LDO and Solid", |
||||
"main": "dist/index.js", |
||||
"scripts": { |
||||
"example": "ts-node ./example/example.ts", |
||||
"build": "tsc --project tsconfig.build.json", |
||||
"watch": "tsc --watch", |
||||
"test": "jest --coverage", |
||||
"test:watch": "jest --watch", |
||||
"prepublishOnly": "npm run test && npm run build", |
||||
"build:ldo": "ldo build --input src/.shapes --output src/.ldo", |
||||
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern", |
||||
"docs": "typedoc --plugin typedoc-plugin-markdown" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/o-development/ldobjects.git" |
||||
}, |
||||
"author": "Jackson Morgan", |
||||
"license": "MIT", |
||||
"bugs": { |
||||
"url": "https://github.com/o-development/ldobjects/issues" |
||||
}, |
||||
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid#readme", |
||||
"devDependencies": { |
||||
"@inrupt/solid-client-authn-core": "^1.17.1", |
||||
"@ldo/cli": "^0.0.1-alpha.17", |
||||
"@rdfjs/data-model": "^1.2.0", |
||||
"@rdfjs/types": "^1.0.1", |
||||
"@solid/community-server": "^6.0.2", |
||||
"@types/jest": "^29.0.3", |
||||
"dotenv": "^16.3.1", |
||||
"jest-rdf": "^1.8.0", |
||||
"ts-jest": "^29.0.2", |
||||
"ts-node": "^10.9.1", |
||||
"typed-emitter": "^2.1.0", |
||||
"typedoc": "^0.25.4", |
||||
"typedoc-plugin-markdown": "^3.17.1" |
||||
}, |
||||
"dependencies": { |
||||
"@inrupt/solid-client": "^1.30.0", |
||||
"@ldo/dataset": "^0.0.1-alpha.17", |
||||
"@ldo/ldo": "^0.0.1-alpha.17", |
||||
"@ldo/rdf-utils": "^0.0.1-alpha.17", |
||||
"@types/parse-link-header": "^2.0.1", |
||||
"cross-fetch": "^3.1.6", |
||||
"http-link-header": "^1.1.1", |
||||
"ts-mixer": "^6.0.3" |
||||
}, |
||||
"files": [ |
||||
"dist", |
||||
"src" |
||||
], |
||||
"publishConfig": { |
||||
"access": "public" |
||||
}, |
||||
"gitHead": "1c242d645fc488f8d0e9f4da7425d9928abf1e9d" |
||||
} |
Loading…
Reference in new issue