Skip to content

Commit

Permalink
Add test
Browse files Browse the repository at this point in the history
  • Loading branch information
Octo8080X committed Jan 2, 2024
1 parent ae60c14 commit eda1ea2
Show file tree
Hide file tree
Showing 14 changed files with 380 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ jobs:
- name: view deno.json
run: cat ./deno.json

- name: Start Database on Docker
run: cd docker && docker-compose up -d && cd ..

- name: Run tests
run: deno test --unstable --allow-read --allow-write --allow-env --allow-net --no-check --coverage=./coverage

Expand Down
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
[![Made with Fresh](https://fresh.deno.dev/fresh-badge-dark.svg)](https://fresh.deno.dev)
# Plantation 🍋

# Plantation
Authentication plugin for Fresh(Deno) using Lucia.

Authentication plugin for Fresh(Deno) using Lucia.
Plantation is inspired by [devise](https://github.com/heartcombo/devise).
Combining plantation with fresh provides the ability to create accounts, log in
and log out.

[![Made with Fresh](https://fresh.deno.dev/fresh-badge-dark.svg)](https://fresh.deno.dev)

# Usage

Expand Down Expand Up @@ -47,6 +50,11 @@ export default defineConfig({
});
```

## permission

- read
- write(when doing cli)

## Custom handler and component

You can use the cli tool to build your own handlers and components.
Expand All @@ -58,8 +66,8 @@ Create File: ./plantation/user/login.tsx
Create File: ./plantation/user/logout.tsx
```

When using the user resource, set `resourceName: "user"`.
At this time, if plantation/user/create.tsx(login.tsx, logout.tsx) is available, it is referenced first.
When using the user resource, set `resourceName: "user"`.\
At this time, if plantation/user/create.tsx(login.tsx, logout.tsx) is available,
it is referenced first.

Use this for your own customization of CSS or for more detailed customization.

6 changes: 3 additions & 3 deletions cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Deno.writeTextFileSync(
createFilePath,
await create.text(),
);
console.info(`✅ Create File: ${createFilePath}`)
console.info(`✅ Create File: ${createFilePath}`);

const login = await fetch(
"https://raw.githubusercontent.com/Octo8080X/plantation/main/routesTemplate/create.tsx",
Expand All @@ -26,7 +26,7 @@ Deno.writeTextFileSync(
loginFilePath,
await login.text(),
);
console.info(`✅ Create File: ${loginFilePath}`)
console.info(`✅ Create File: ${loginFilePath}`);

const logout = await fetch(
"https://raw.githubusercontent.com/Octo8080X/plantation/main/routesTemplate/logout.tsx",
Expand All @@ -37,4 +37,4 @@ Deno.writeTextFileSync(
logoutFilePath,
await logout.text(),
);
console.info(`✅ Create File: ${logoutFilePath}`)
console.info(`✅ Create File: ${logoutFilePath}`);
10 changes: 10 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: "3"
services:
db:
image: mysql:8.0
command: mysqld --default-authentication-plugin=mysql_native_password
ports:
- "${MYSQL_PORT:-3306}:3306"
environment:
- MYSQL_DATABASE=test
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-password_root}
20 changes: 20 additions & 0 deletions must_login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//import { WithCsrf } from "https://deno.land/x/[email protected]/mod.ts";
import { LogoutForm, PageProps, WithCsrf } from "plantation/mod.ts";
//import { PageProps } from "../../types.ts";

export default function Home(props: PageProps<unknown, WithCsrf>) {
return (
<div class="px-4 py-8 mx-auto bg-[#86efac]">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
MUST LOGIN
<LogoutForm
actionPath="/user/logout"
csrfToken={props.state.csrf.getTokenStr()}
class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 transition-colors"
>
<p>Logout</p>
</LogoutForm>
</div>
</div>
);
}
20 changes: 20 additions & 0 deletions must_session.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//import { WithCsrf } from "https://deno.land/x/[email protected]/mod.ts";
import { LogoutForm, PageProps, WithCsrf } from "plantation/mod.ts";
//import { PageProps } from "../../types.ts";

export default function Home(props: PageProps<unknown, WithCsrf>) {
return (
<div class="px-4 py-8 mx-auto bg-[#86efac]">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
MUST LOGIN
<LogoutForm
actionPath="/user/logout"
csrfToken={props.state.csrf.getTokenStr()}
class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 transition-colors"
>
<p>Logout</p>
</LogoutForm>
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion routesTemplate/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export function getCreateComponent(
return (
<div style={styles.block}>
<div>
<h2>Create Account</h2>
<h2>Custom Create Account</h2>
</div>
<div>
<form action={paths.createPath} method="post">
Expand Down
2 changes: 1 addition & 1 deletion src/components/LogoutForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { JSX, h } from "preact";
import { h, JSX } from "preact";

interface LogoutFormProps extends JSX.HTMLAttributes<HTMLButtonElement> {
csrfToken: string;
Expand Down
35 changes: 35 additions & 0 deletions tests/config/test_fresh.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/// <reference lib="deno.unstable" />
import { defineConfig } from "$fresh/server.ts";
import { testPlugin } from "../plugins/test_plugin.ts";

import { getPlantationWithCsrfPlugins } from "../../mod.ts";
import { auth, connectionPool } from "../utils/auth.ts";
export { connectionPool };
import { z } from "../../deps.ts";

const testEmailSchema = z.coerce.string().email();
const testPasswordSchema = z.coerce.string().trim().min(8);

export default defineConfig({
plugins: [
...(await getPlantationWithCsrfPlugins(
{
csrf: {
kv: await Deno.openKv(":memory:"),
},
plantationParams: {
setupRootPath: "/",
auth: auth,
allowNoSessionPaths: [],
resourceName: "user",
resourceIdentifierName: "email",
loginAfterPath: "/must_login",
logoutAfterPath: "/",
identifierSchema: testEmailSchema,
passwordSchema: testPasswordSchema,
},
},
)),
testPlugin,
],
});
13 changes: 13 additions & 0 deletions tests/plugins/test_plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { PageProps, Plugin } from "$fresh/server.ts";
import TestComponent from "../routes/test_route.tsx";
import { ComponentType } from "preact";

export const testPlugin: Plugin = {
name: "TestPlugin",
routes: [
{
component: TestComponent as ComponentType<PageProps>,
path: "/must_login",
},
],
};
18 changes: 18 additions & 0 deletions tests/routes/test_route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { LogoutForm, PageProps, WithCsrf } from "../../mod.ts";

export default function MustLogin(props: PageProps<unknown, WithCsrf>) {
return (
<div class="px-4 py-8 mx-auto bg-[#86efac]">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
MUST LOGIN
<LogoutForm
actionPath="/user/logout"
csrfToken={props.state.csrf.getTokenStr()}
class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 transition-colors"
>
<p>Logout</p>
</LogoutForm>
</div>
</div>
);
}
182 changes: 182 additions & 0 deletions tests/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { createHandler, ServeHandlerInfo } from "$fresh/server.ts";
import manifest from "./work/fresh.gen.ts";
import config, { connectionPool } from "./config/test_fresh.config.ts";
import { expect } from "./test_deps.ts";

const CONN_INFO: ServeHandlerInfo = {
remoteAddr: { hostname: "127.0.0.1", port: 53496, transport: "tcp" },
};

Deno.test("Response Test", async (t) => {
// const handler = await createHandler(manifest, config);

await t.step("No login => Redirect", async () => {
const handler = await createHandler(manifest, config);
let resp = await handler(
new Request("http://127.0.0.1/"),
CONN_INFO,
);

expect(resp.status).toBe(302);
expect(resp.headers.get("location")).toBe("/user/login");
});

await t.step("/user/login has /user/create link", async () => {
const handler = await createHandler(manifest, config);
let resp = await handler(
new Request("http://127.0.0.1/user/login"),
CONN_INFO,
);

expect(resp.status).toBe(200);
const text = await resp.text();
expect(text.includes('href="/user/create"')).toBe(true);
});

await t.step("/user/create has /user/login link", async () => {
const handler = await createHandler(manifest, config);
let resp = await handler(
new Request("http://127.0.0.1/user/create"),
CONN_INFO,
);

expect(resp.status).toBe(200);
const text = await resp.text();
expect(text.includes('href="/user/login"')).toBe(true);
});
//
//
// await t.step("Not Work Session(incorrect cookie)", async () => {
// let resp = await handler(
// new Request("http://127.0.0.1/session"),
// CONN_INFO,
// );
// assertEquals(resp.status, 200);
//
// let text = await resp.text();
// assertEquals(text.includes("<p>count:0</p>"), true);
//
// const sessionKey =
// (resp.headers.get("set-cookie")!).split("session=")[1].split(";")[0];
//
// resp = await handler(
// new Request("http://127.0.0.1/session", {
// headers: { cookie: `session=${sessionKey}AA` },
// }),
// CONN_INFO,
// );
// assertEquals(resp.status, 200);
// text = await resp.text();
// assertEquals(text.includes("<p>count:0</p>"), true);
// });
});

Deno.test(
{
name: "Login test",
async fn(t) {
const handler = await createHandler(manifest, config);
let csrfCookieToken = "";
let csrfToken = "";
let authSession = "";

await t.step("Create account", async () => {
let resp = await handler(
new Request("http://127.0.0.1/user/create"),
CONN_INFO,
);

const text = await resp.text();
csrfCookieToken = resp.headers
.get("set-cookie")!
.split("csrf_token=")[1]
.split(";")[0];
csrfToken = text
.split('<input type="hidden" name="csrf" value="')[1]
.split('"/')[0];

const formData = new FormData();
formData.append("csrf", csrfToken);
formData.append("email", "[email protected]");
formData.append("password", "password");

const headers = new Headers();
headers.set("cookie", `csrf_token=${csrfCookieToken}`);

resp = await handler(
new Request("http://127.0.0.1/user/create", {
headers,
method: "POST",
body: formData,
}),
CONN_INFO,
);

expect(resp.status).toBe(302);
expect(resp.headers.get("location")).toBe("/must_login");
});
await t.step("Login", async () => {
const formData = new FormData();
formData.append("csrf", csrfToken);
formData.append("email", "[email protected]");
formData.append("password", "password");

const headers = new Headers();
headers.set("cookie", `csrf_token=${csrfCookieToken}`);

const resp = await handler(
new Request("http://127.0.0.1/user/login", {
headers,
method: "POST",
body: formData,
}),
CONN_INFO,
);

expect(resp.status).toBe(302);
expect(resp.headers.get("location")).toBe("/must_login");
authSession =
resp.headers.get("set-cookie")!.split("auth_session=")[1].split(
";",
)[0];
});

await t.step("Logout", async () => {
// const formData = new FormData();
// formData.append("csrf", csrfToken);
// formData.append("email", "[email protected]");
// formData.append("password", "password");

const headers = new Headers();
headers.set("cookie", `auth_session=${authSession}`);

let resp = await handler(
new Request("http://127.0.0.1/must_login", {
headers,
}),
CONN_INFO,
);

expect(resp.status).toBe(200);
const text = await resp.text();
expect(text.includes("MUST LOGIN")).toBe(true);

const formData = new FormData();
formData.append("csrf", csrfToken);

resp = await handler(
new Request("http://127.0.0.1/user/logout", {
headers,
method: "POST",
body: formData,
}),
CONN_INFO,
);
expect(resp.status).toBe(302);
expect(resp.headers.get("location")).toBe("/user/login");
});
},
sanitizeOps: false,
sanitizeResources: false,
},
);
2 changes: 2 additions & 0 deletions tests/test_deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { expect } from "https://deno.land/[email protected]/expect/mod.ts";
export { FakeTime } from "https://deno.land/[email protected]/testing/time.ts";
Loading

0 comments on commit eda1ea2

Please sign in to comment.