Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions myhaki/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"start": "next start",
"lint": "eslint",
"test": "jest"

},
"dependencies": {
"@heroicons/react": "^2.2.0",
Expand Down
32 changes: 15 additions & 17 deletions myhaki/src/app/api/login/route.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import { NextResponse } from "next/server";
import { error } from "console";

const baseUrl = process.env.BASE_URL;

export async function POST(request: Request) {
try {
const { email, password } = await request.json();

if (!email || !password) {
return NextResponse.json({ detail: "Email and password are required." }, { status: 400 });
}

if (!baseUrl) {
return NextResponse.json({ detail: "BASE_URL environment variable not set" }, { status: 500 });
}

const response = await fetch(`${baseUrl}/api/login/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
const response = await fetch(`${baseUrl}/login/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});

const result = await response.json();

return NextResponse.json(result, { status: response.status });
if (!response.ok) {
return new Response(JSON.stringify(result), { status: response.status });
}

return new Response(JSON.stringify(result), { status: 200 });
} catch (error) {
return NextResponse.json(
{ detail: (error as Error).message || "Internal Server Error" },
{ status: 500 }
);
return new Response(JSON.stringify({ error: (error as Error).message }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}


70 changes: 70 additions & 0 deletions myhaki/src/app/api/users/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { NextRequest } from 'next/server';

const baseUrl = process.env.BASE_URL;

export async function GET(request: NextRequest) {
const id = request.nextUrl.pathname.split('/').filter(Boolean).pop();

if (!id) {
return new Response(JSON.stringify({ error: 'User ID is required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}

try {
const token = request.headers.get('authorization');
const headers: HeadersInit = {};
if (token) headers['Authorization'] = token;

const response = await fetch(`${baseUrl}/users/${id}/`, { headers });
const result = await response.json();
return new Response(JSON.stringify(result), {
status: response.status,
headers: { 'Content-Type': 'application/json' },
});
} catch (error: any) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}

export async function PATCH(request: NextRequest) {

const id = request.nextUrl.pathname.split('/').filter(Boolean).pop();

if (!id) {
return new Response(JSON.stringify({ error: 'User ID is required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}

try {
const body = await request.json();
const token = request.headers.get('authorization');
const headers: HeadersInit = {
'Content-Type': 'application/json',
};
if (token) headers['Authorization'] = token;

const response = await fetch(`${baseUrl}/users/${id}/`, {
method: 'PATCH',
headers,
body: JSON.stringify(body),
});

const result = await response.json();
return new Response(JSON.stringify(result), {
status: response.status,
headers: { 'Content-Type': 'application/json' },
});
}catch (error) {
return new Response(JSON.stringify({ error: (error as Error).message }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
79 changes: 0 additions & 79 deletions myhaki/src/app/api/users/route.ts

This file was deleted.

107 changes: 64 additions & 43 deletions myhaki/src/app/authentication/sign-in/page.test.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { render, screen, fireEvent, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import SignInPage from "./page";

jest.mock("next/image", () => (props: any) => <img {...props} alt={props.alt} />);
jest.mock("next/image", () => ({
__esModule: true,
default: (props: React.ImgHTMLAttributes<HTMLImageElement>) => <img {...props} />,
}));

jest.mock('@/app/hooks/useFetchSignIn', () => {
const React = require('react');
return {
__esModule: true,
default: jest.fn(() => {
const [email, setEmail] = React.useState('');
const [password, setPassword] = React.useState('');
const [error, setError] = React.useState('');
const [message, setMessage] = React.useState('');
const signin = jest.fn();
return { email, setEmail, password, setPassword, error, message, signin };
}),
};
});

jest.mock('@/app/hooks/useFetchSignIn', () => ({
useSignIn: jest.fn(),
jest.mock('next/navigation', () => ({
useRouter: () => ({
push: jest.fn(),
replace: jest.fn(),
prefetch: jest.fn(() => Promise.resolve()),
pathname: '/sign-in',
}),
usePathname: () => '/sign-in',
useSearchParams: () => new URLSearchParams(),
}));

import { useSignIn } from '@/app/hooks/useFetchSignIn';
import useFetchSignin from '@/app/hooks/useFetchSignIn';

describe("SignInPage", () => {
const mockHandleSignIn = jest.fn();
const setEmail = jest.fn();
const setPassword = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
(useSignIn as jest.Mock).mockReturnValue({
email: "",
setEmail,
password: "",
setPassword,
error: "",
message: "",
handleSignIn: mockHandleSignIn,
});
});

it("renders form inputs and buttons", () => {
render(<SignInPage />);
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
Expand All @@ -36,59 +45,71 @@ describe("SignInPage", () => {
expect(screen.getByRole("button", { name: /show password/i })).toBeInTheDocument();
});

it("toggles password visibility on button click", () => {
it("toggles password visibility on button click", async () => {
render(<SignInPage />);
const passwordInput = screen.getByLabelText(/^password$/i);
const toggleButton = screen.getByRole("button", { name: /show password/i });

expect(passwordInput).toHaveAttribute("type", "password");

fireEvent.click(toggleButton);
await userEvent.click(toggleButton);
expect(toggleButton).toHaveAttribute("aria-label", "Hide password");
expect(passwordInput).toHaveAttribute("type", "text");

fireEvent.click(toggleButton);
await userEvent.click(toggleButton);
expect(toggleButton).toHaveAttribute("aria-label", "Show password");
expect(passwordInput).toHaveAttribute("type", "password");
});

it("calls setEmail and setPassword on input changes", () => {
it("calls setEmail and setPassword on input changes", async () => {
render(<SignInPage />);
fireEvent.change(screen.getByLabelText(/email/i), { target: { value: "user@test.com" } });
expect(setEmail).toHaveBeenCalledWith("user@test.com");
const emailInput = screen.getByLabelText(/email/i);
await userEvent.type(emailInput, "user@test.com");
expect(emailInput).toHaveValue("user@test.com");

fireEvent.change(screen.getByLabelText(/^password$/i), { target: { value: "mypassword" } });
expect(setPassword).toHaveBeenCalledWith("mypassword");
const passwordInput = screen.getByLabelText(/^password$/i);
await userEvent.type(passwordInput, "mypassword");
expect(passwordInput).toHaveValue("mypassword");
});

it("calls handleSignIn on form submission", async () => {
mockHandleSignIn.mockResolvedValueOnce(undefined);
it("calls signin on form submission", async () => {
const signinMock = jest.fn();
(useFetchSignin as jest.Mock).mockReturnValue({
email: '',
setEmail: jest.fn(),
password: '',
setPassword: jest.fn(),
error: '',
message: '',
signin: signinMock,
});

const { container } = render(<SignInPage />);
const form = container.querySelector("form");
if (!form) throw new Error("Form element not found");
const form = container.querySelector('form');
if (!form) throw new Error('Form element not found');

fireEvent.submit(form);

await waitFor(() => {
expect(mockHandleSignIn).toHaveBeenCalled();
expect(signinMock).toHaveBeenCalled();
});
});

it("shows error and message when provided", () => {
(useSignIn as jest.Mock).mockReturnValue({
email: "",
(useFetchSignin as jest.Mock).mockReturnValue({
email: '',
setEmail: jest.fn(),
password: "",
password: '',
setPassword: jest.fn(),
error: "Invalid credentials",
message: "Welcome back!",
handleSignIn: mockHandleSignIn,
error: 'Invalid credentials',
signin: jest.fn(),
});

render(<SignInPage />);

expect(screen.getByText("Invalid credentials")).toBeInTheDocument();
expect(screen.getByText("Welcome back!")).toBeInTheDocument();
expect(screen.getByText('Invalid credentials')).toBeInTheDocument();

const container = screen.getByText('Invalid credentials').parentElement;
expect(container).not.toBeNull();
});
});
Loading