Skip to content

Commit

Permalink
Prune testdrives before running e2e
Browse files Browse the repository at this point in the history
  • Loading branch information
Polleps committed Oct 13, 2023
1 parent 623b0e6 commit 574352e
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 6 deletions.
40 changes: 40 additions & 0 deletions browser/data-browser/src/routes/PruneTestsRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Resource, Server, useStore } from '@tomic/react';
import React, { useState } from 'react';
import { Button } from '../components/Button';
import { ContainerFull } from '../components/Containers';
import { Column } from '../components/Row';

export function PruneTestsRoute(): JSX.Element {
const store = useStore();
const [result, setResult] = useState<Resource<Server.EndpointResponse>>();
const [isWaiting, setIsWaiting] = useState(false);

const postPruneTest = async () => {
setIsWaiting(true);
const url = new URL('/prunetests', store.getServerUrl());
const res = await store.postToServer(url.toString());
setIsWaiting(false);
setResult(res);
};

return (
<main>
<ContainerFull>
<h1>Prune Test Data</h1>
<p>
Pruning test data will delete all drives on the server that have
&rsquo;testdrive&rsquo; in their name.
</p>
<Column>
<Button onClick={postPruneTest} disabled={isWaiting} alert>
Prune
</Button>
{isWaiting && <p>Pruning, this might take a while...</p>}
<p data-testId='prune-result'>
{result && `✅ ${result.props.responseMessage}`}
</p>
</Column>
</ContainerFull>
</main>
);
}
2 changes: 2 additions & 0 deletions browser/data-browser/src/routes/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Sandbox } from './Sandbox';
import { TokenRoute } from './TokenRoute';
import { ImporterPage } from '../views/ImporterPage';
import { History } from './History';
import { PruneTestsRoute } from './PruneTestsRoute';

const homeURL = window.location.origin;

Expand Down Expand Up @@ -48,6 +49,7 @@ export function AppRoutes(): JSX.Element {
<Route path={paths.search} element={<Search />} />
<Route path={paths.token} element={<TokenRoute />} />
<Route path={paths.history} element={<History />} />
{isDev && <Route path={paths.pruneTests} element={<PruneTestsRoute />} />}
{isDev && <Route path={paths.sandbox} element={<Sandbox />} />}
<Route path='/' element={<ResourcePage subject={homeURL} />} />
<Route path='*' element={<Local />} />
Expand Down
1 change: 1 addition & 0 deletions browser/data-browser/src/routes/paths.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export const paths = {
allVersions: '/all-versions',
sandbox: '/sandbox',
fetchBookmark: '/fetch-bookmark',
pruneTests: '/prunetests',
};
12 changes: 12 additions & 0 deletions browser/data-browser/tests/global.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { test as setup, expect } from '@playwright/test';
import { before, FRONTEND_URL, signIn } from './test-utils';

setup('delete previous test data', async ({ page }) => {
await before({ page });
await signIn(page);
await page.goto(`${FRONTEND_URL}/prunetests`);
await expect(page.getByText('Prune Test Data')).toBeVisible();
await page.getByRole('button', { name: 'Prune' }).click();

await expect(page.getByTestId('prune-result')).toBeVisible();
});
5 changes: 5 additions & 0 deletions browser/data-browser/tests/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ const config: PlaywrightTestConfig = {
retries: 3,
// timeout: 1000 * 120, // 2 minutes
projects: [
{
name: 'setup',
testMatch: /global.setup\.ts/,
},
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
dependencies: ['setup'],
},
],
// projects: [
Expand Down
4 changes: 2 additions & 2 deletions browser/lib/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export class Client {
subject: string,
opts: FetchResourceOptions = {},
): Promise<HTTPResourceResult> {
const { signInfo, from, body: bodyReq } = opts;
const { signInfo, from, body: bodyReq, method } = opts;
let createdResources: Resource[] = [];
const parser = new JSONADParser();
let resource = new Resource(subject);
Expand Down Expand Up @@ -160,7 +160,7 @@ export class Client {

const response = await this.fetch(url, {
headers: requestHeaders,
method: bodyReq ? 'POST' : 'GET',
method: method ?? 'GET',
body: bodyReq,
});
const body = await response.text();
Expand Down
17 changes: 17 additions & 0 deletions browser/lib/src/ontologies/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const server = {
redirect: 'https://atomicdata.dev/classes/Redirect',
file: 'https://atomicdata.dev/classes/File',
invite: 'https://atomicdata.dev/classes/Invite',
endpointResponse:
'https://atomicdata.dev/ontology/server/class/endpoint-response',
},
properties: {
drives: 'https://atomicdata.dev/properties/drives',
Expand All @@ -35,6 +37,9 @@ export const server = {
children: 'https://atomicdata.dev/properties/children',
parameters: 'https://atomicdata.dev/properties/endpoint/parameters',
destination: 'https://atomicdata.dev/properties/destination',
status: 'https://atomicdata.dev/ontology/server/property/status',
responseMessage:
'https://atomicdata.dev/ontology/server/property/response-message',
},
} as const;

Expand All @@ -46,6 +51,7 @@ export namespace Server {
export type Redirect = typeof server.classes.redirect;
export type File = typeof server.classes.file;
export type Invite = typeof server.classes.invite;
export type EndpointResponse = typeof server.classes.endpointResponse;
}

declare module '../index.js' {
Expand Down Expand Up @@ -92,6 +98,13 @@ declare module '../index.js' {
| typeof server.properties.users
| typeof server.properties.usagesLeft;
};
[server.classes.endpointResponse]: {
requires:
| BaseProps
| typeof server.properties.status
| typeof server.properties.responseMessage;
recommends: never;
};
}

interface PropTypeMapping {
Expand All @@ -116,6 +129,8 @@ declare module '../index.js' {
[server.properties.children]: string[];
[server.properties.parameters]: string[];
[server.properties.destination]: string;
[server.properties.status]: number;
[server.properties.responseMessage]: string;
}

interface PropSubjectToNameMapping {
Expand All @@ -140,5 +155,7 @@ declare module '../index.js' {
[server.properties.children]: 'children';
[server.properties.parameters]: 'parameters';
[server.properties.destination]: 'destination';
[server.properties.status]: 'status';
[server.properties.responseMessage]: 'responseMessage';
}
}
7 changes: 4 additions & 3 deletions browser/lib/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
FileOrFileLike,
OptionalClass,
UnknownClass,
Server,
} from './index.js';
import { authenticate, fetchWebSocket, startWebsocket } from './websockets.js';

Expand Down Expand Up @@ -533,10 +534,10 @@ export class Store {
}

/** Sends an HTTP POST request to the server to the Subject. Parses the returned Resource and adds it to the store. */
public async postToServer(
public async postToServer<R extends OptionalClass = Server.EndpointResponse>(
url: string,
data: ArrayBuffer | string,
): Promise<Resource> {
data?: ArrayBuffer | string,
): Promise<Resource<R>> {
return this.fetchResourceFromServer(url, {
body: data,
noWebSocket: true,
Expand Down
2 changes: 2 additions & 0 deletions lib/src/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,7 @@ pub fn default_endpoints() -> Vec<Endpoint> {
plugins::bookmark::bookmark_endpoint(),
plugins::importer::import_endpoint(),
plugins::query::query_endpoint(),
#[cfg(debug_assertions)]
plugins::prunetests::prune_tests_endpoint(),
]
}
1 change: 1 addition & 0 deletions lib/src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub mod invite;
pub mod bookmark;
pub mod files;
pub mod path;
pub mod prunetests;
pub mod query;
pub mod search;
pub mod versioning;
70 changes: 70 additions & 0 deletions lib/src/plugins/prunetests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use crate::{
endpoints::{Endpoint, HandleGetContext, HandlePostContext},
errors::AtomicResult,
storelike::Query,
urls, Resource, Storelike, Value,
};

pub fn prune_tests_endpoint() -> Endpoint {
Endpoint {
path: urls::PATH_PRUNE_TESTS.into(),
params: [].into(),
description: "Deletes all drives with 'testdrive-' in their name.".to_string(),
shortname: "prunetests".to_string(),
handle: Some(handle_get),
handle_post: Some(handle_prune_tests_request),
}
}

pub fn handle_get(context: HandleGetContext) -> AtomicResult<Resource> {
prune_tests_endpoint().to_resource(context.store)
}

// Delete all drives with 'testdrive-' in their name. (These drive are generated with each e2e test run)
fn handle_prune_tests_request(context: HandlePostContext) -> AtomicResult<Resource> {
let HandlePostContext { store, .. } = context;

let mut query = Query::new_class(urls::DRIVE);
query.for_agent = context.for_agent.clone();
let mut deleted_drives = 0;

if let Ok(mut query_result) = store.query(&query) {
println!(
"Received prune request, deleting {} drives",
query_result.resources.len()
);

let total_drives = query_result.resources.len();

for resource in query_result.resources.iter_mut() {
if let Value::String(name) = resource
.get(urls::NAME)
.unwrap_or(&Value::String("".to_string()))
{
if name.contains("testdrive-") {
resource.destroy(store)?;
deleted_drives += 1;

if (deleted_drives % 10) == 0 {
println!("Deleted {} of {} drives", deleted_drives, total_drives);
}
}
}
}

println!("Done pruning drives");
} else {
println!("Received prune request but there are no drives to prune");
}

let resource = build_response(store, 200, format!("Deleted {} drives", deleted_drives));
Ok(resource)
}

fn build_response(store: &impl Storelike, status: i32, message: String) -> Resource {
let mut resource = Resource::new_generate_subject(store);
resource.set_class(urls::ENDPOINT_RESPONSE);
resource.set_propval_unsafe(urls::STATUS.to_string(), status.into());
resource.set_propval_unsafe(urls::RESPONSE_MESSAGE.to_string(), message.into());
resource
}
8 changes: 7 additions & 1 deletion lib/src/urls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub const IMPORTER: &str = "https://atomicdata.dev/classes/Importer";
pub const ERROR: &str = "https://atomicdata.dev/classes/Error";
pub const BOOKMARK: &str = "https://atomicdata.dev/class/Bookmark";
pub const ONTOLOGY: &str = "https://atomicdata.dev/class/ontology";
pub const ENDPOINT_RESPONSE: &str =
"https://atomicdata.dev/ontology/server/class/endpoint-response";

// Properties
pub const SHORTNAME: &str = "https://atomicdata.dev/properties/shortname";
Expand Down Expand Up @@ -119,7 +121,10 @@ pub const LOCAL_ID: &str = "https://atomicdata.dev/properties/localId";
pub const PROPERTIES: &str = "https://atomicdata.dev/properties/properties";
pub const CLASSES: &str = "https://atomicdata.dev/properties/classes";
pub const INSTANCES: &str = "https://atomicdata.dev/properties/instances";

// ... for Endpoint-Response
pub const STATUS: &str = "https://atomicdata.dev/ontology/server/property/status";
pub const RESPONSE_MESSAGE: &str =
"https://atomicdata.dev/ontology/server/property/response-message";
// Datatypes
pub const STRING: &str = "https://atomicdata.dev/datatypes/string";
pub const MARKDOWN: &str = "https://atomicdata.dev/datatypes/markdown";
Expand Down Expand Up @@ -150,3 +155,4 @@ pub fn construct_path_import(base: &str) -> String {
pub const PATH_IMPORT: &str = "/import";
pub const PATH_FETCH_BOOKMARK: &str = "/fetch-bookmark";
pub const PATH_QUERY: &str = "/query";
pub const PATH_PRUNE_TESTS: &str = "/prunetests";

0 comments on commit 574352e

Please sign in to comment.