Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(profile): Make usernames case insensitive #216

Merged
merged 10 commits into from
Apr 20, 2023
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 () => {
ff6347 marked this conversation as resolved.
Show resolved Hide resolved
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