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 = { |
module.exports = { |
||||||
...sharedConfig, |
...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