Skip to content

Commit

Permalink
Merge pull request #216 from technologiestiftung/fix/unique-ci-usernames
Browse files Browse the repository at this point in the history
fix(profile): Make usernames case insensitive
  • Loading branch information
ff6347 authored Apr 20, 2023
2 parents 8ba3a71 + df78e61 commit c2c0a2c
Show file tree
Hide file tree
Showing 11 changed files with 791 additions and 415 deletions.
8 changes: 6 additions & 2 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"recommendations": ["humao.rest-client", "mikestead.dotenv"]
}
"recommendations": [
"humao.rest-client",
"mikestead.dotenv",
"mkhl.direnv"
]
}
7 changes: 4 additions & 3 deletions __test-utils/postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ export async function truncateTreesAdopted() {
sql.end();
}

export async function createWateredTrees() {
export async function createWateredTrees(userId?: string, userName?: string) {
const sql = postgres(url);
const randomText = sql`md5(random()::text)`;
await sql`
INSERT INTO trees_watered (uuid, amount, timestamp, username, tree_id)
SELECT
md5(random()::text),
${userId ? userId : sql`extensions.uuid_generate_v4()::text`},
random() * 10,
NOW() - (random() * INTERVAL '7 days'),
md5(random()::text),
${userName ? userName : randomText},
id
FROM
trees
Expand Down
20 changes: 13 additions & 7 deletions __test-utils/req-test-token.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { SignupResponse } from "../_types/user";
import { SUPABASE_ANON_KEY, SUPABASE_URL } from "./supabase";
const issuer = process.env.issuer || "";
const client_id = process.env.client_id || "";
const client_secret = process.env.client_secret || "";
const audience = process.env.audience || "";
const SUPABASE_URL = process.env.SUPABASE_URL || "http://localhost:54321";
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || "";

export async function requestAuth0TestToken() {
const response = await fetch(`${issuer}oauth/token`, {
method: "POST",
Expand Down Expand Up @@ -47,14 +48,15 @@ export async function requestSupabaseTestToken(
const json = await response.text();
throw new Error(`Could not get test token, ${json}`);
}
const json = (await response.json()) as {
access_token: string;
user: { id: string };
};
const json = (await response.json()) as SignupResponse;
return json.access_token;
}

export async function createSupabaseUser(email: string, password: string) {
export async function createSupabaseUser(
email: string,
password: string,
opts?: { returnFullUser: boolean }
) {
const response = await fetch(`${SUPABASE_URL}/auth/v1/signup`, {
method: "POST",
headers: {
Expand All @@ -67,12 +69,16 @@ export async function createSupabaseUser(email: string, password: string) {
}),
});
if (!response.ok) {
console.log(response.status);
const json = await response.text();
throw new Error(`Could not create test user, ${json}`);
}
const json = (await response.json()) as {
access_token: string;
user: { id: string };
};
if (opts?.returnFullUser) {
return json;
}
return json.access_token;
}
16 changes: 16 additions & 0 deletions __test-utils/supabase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createClient } from "@supabase/supabase-js";
import { Database } from "../_types/database";
export const SUPABASE_URL =
process.env.SUPABASE_URL || "http://localhost:54321";
export const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || "";
export const SUPABASE_SERVICE_ROLE_KEY =
process.env.SUPABASE_SERVICE_ROLE_KEY || "";

export const supabaseAnonClient = createClient<Database>(
SUPABASE_URL,
SUPABASE_ANON_KEY
);
export const supabaseServiceRoleClient = createClient<Database>(
SUPABASE_URL,
SUPABASE_SERVICE_ROLE_KEY
);
189 changes: 189 additions & 0 deletions __tests__/schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import {
deleteSupabaseUser,
truncateTreesWaterd,
} from "../__test-utils/postgres";
import {
SUPABASE_ANON_KEY,
SUPABASE_URL,
supabaseAnonClient,
supabaseServiceRoleClient,
} from "../__test-utils/supabase";
describe("misc test testing the schema function of the database", () => {
test("inserting an existing username should alter the new name and add a uuid at end", async () => {
const email1 = "someone@email.com";
const email2 = "someone@foo.com";
await deleteSupabaseUser(email1);
await deleteSupabaseUser(email2);
const password = "12345678";
const { data: user1, error } = await supabaseAnonClient.auth.signUp({
email: email1,
password: password,
});
const { data: user2, error: error2 } = await supabaseAnonClient.auth.signUp(
{
email: email2,
password: password,
}
);
expect(error).toBeNull();
expect(user1).toBeDefined();
expect(error2).toBeNull();
expect(user2).toBeDefined();

const { data: users, error: usersError } = await supabaseAnonClient
.from("profiles")
.select("*")
.in("id", [user1?.user?.id, user2?.user?.id]);

expect(usersError).toBeNull();
expect(users).toHaveLength(2);
expect(users?.[0].username).toBe("someone");
expect(users?.[1].username).not.toBe("someone");
expect(users?.[1].username).toContain("someone-");
expect(users?.[1].username).toMatch(/^someone-[a-zA-Z0-9]{6}$/);
await deleteSupabaseUser(email1);
await deleteSupabaseUser(email2);
});

test("a user should be able to remove its account and his associated data", async () => {
const numberOfTrees = 10;
const email = "user@email.com";
await deleteSupabaseUser(email); // clean up before running
const { data, error } = await supabaseAnonClient.auth.signUp({
email: email,
password: "12345678",
});
expect(error).toBeNull();
expect(data).toBeDefined();
const { data: trees, error: treesError } = await supabaseAnonClient
.from("trees")
.select("*")
.limit(numberOfTrees);
expect(treesError).toBeNull();
expect(trees).toHaveLength(numberOfTrees);

const { data: adoptedTrees, error: adoptedTreesError } =
await supabaseServiceRoleClient
.from("trees_adopted")
.insert(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
trees!.map((tree) => ({
uuid: data.user?.id,
tree_id: tree.id,
}))
)
.select("*");
expect(adoptedTreesError).toBeNull();
expect(adoptedTrees).toHaveLength(numberOfTrees);
const { data: userTrees, error: userTreesError } =
await supabaseServiceRoleClient
.from("trees_watered")
.insert(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
trees!.map((tree) => ({
uuid: data.user?.id,
amount: 1,
timestamp: new Date().toISOString(),
username: "user",
tree_id: tree.id,
}))
)
.select("*");
expect(userTreesError).toBeNull();
expect(userTrees).toHaveLength(numberOfTrees);

// since wa can not pass the token to our supabase client, we need to use fetch directly
const response = await fetch(`${SUPABASE_URL}/rest/v1/rpc/remove_account`, {
method: "POST",
headers: {
apikey: SUPABASE_ANON_KEY,
"Content-Type": "application/json",
Authorization: `Bearer ${data.session?.access_token}`,
},
});
expect(response.ok).toBeTruthy();
expect(response.status).toBe(204);
const { data: treesAfter, error: treesAfterError } =
await supabaseAnonClient
.from("trees_watered")
.select("*")
.eq("uuid", data.user?.id);
expect(treesAfterError).toBeNull();
expect(treesAfter).toHaveLength(0);

const { data: adoptedTreesAfter, error: adoptedTreesAfterError } =
await supabaseAnonClient
.from("trees_adopted")
.select("*")
.eq("uuid", data.user?.id);
expect(adoptedTreesAfterError).toBeNull();
expect(adoptedTreesAfter).toHaveLength(0);
await truncateTreesWaterd();
});

test("if a user changes his username all the usernames on the trees_watered table should change too", async () => {
const email = "foo@bar.com";
const numberOfTrees = 10;
await deleteSupabaseUser(email);
await truncateTreesWaterd();
const { data, error } = await supabaseAnonClient.auth.signUp({
email: email,
password: "12345678",
});
expect(error).toBeNull();
expect(data).toBeDefined();
const { data: trees, error: treesError } = await supabaseAnonClient
.from("trees")
.select("*")
.limit(numberOfTrees);
expect(treesError).toBeNull();
expect(trees).toHaveLength(numberOfTrees);

const { data: adoptedTrees, error: adoptedTreesError } =
await supabaseServiceRoleClient
.from("trees_watered")
.insert(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
trees!.map((tree) => ({
uuid: data.user?.id,
tree_id: tree.id,
amount: 1,
timestamp: new Date().toISOString(),
username: "foo",
}))
)
.select("*");
expect(adoptedTreesError).toBeNull();
expect(adoptedTrees).toHaveLength(numberOfTrees);

// since we cant pass our access token to change our username to our anon client we use fetch directly
const changeResponse = await fetch(
`${SUPABASE_URL}/rest/v1/profiles?id=eq.${data?.user?.id}`,
{
method: "PATCH",
headers: {
apikey: SUPABASE_ANON_KEY,
"Content-Type": "application/json",
Authorization: `Bearer ${data.session?.access_token}`,
},
body: JSON.stringify({
username: "bar",
}),
}
);

expect(changeResponse.ok).toBeTruthy();
expect(changeResponse.status).toBe(204);

const { data: treesAfter, error: treesAfterError } =
await supabaseServiceRoleClient
.from("trees_watered")
.select("*")
.eq("username", "bar");

expect(treesAfterError).toBeNull();
expect(treesAfter).toHaveLength(numberOfTrees);
await deleteSupabaseUser(email);
await truncateTreesWaterd();
});
});
Loading

0 comments on commit c2c0a2c

Please sign in to comment.