From b427c4871bc4a9d7d814d9330c27037feee7a372 Mon Sep 17 00:00:00 2001 From: te99y Date: Sat, 7 Dec 2024 10:48:41 -0500 Subject: [PATCH 1/9] added frontend unit tests --- frontend/test/download.test.tsx | 242 +++++++++++++++++--------- frontend/test/history.test.tsx | 181 ++++++++++++++++++++ frontend/test/manageUser.test.tsx | 208 +++++++++++++++++++++++ frontend/test/rating.test.tsx | 274 ++++++++++++++++++++---------- frontend/test/update.test.tsx | 240 ++++++++++++++++++-------- frontend/test/upload.test.tsx | 194 ++++++++++----------- 6 files changed, 995 insertions(+), 344 deletions(-) create mode 100644 frontend/test/history.test.tsx create mode 100644 frontend/test/manageUser.test.tsx diff --git a/frontend/test/download.test.tsx b/frontend/test/download.test.tsx index fbdcbb8..154b262 100644 --- a/frontend/test/download.test.tsx +++ b/frontend/test/download.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import '@testing-library/jest-dom'; -import { MemoryRouter } from 'react-router-dom'; -import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; // Import MemoryRouter import DownloadPage from '../src/pages/download'; beforeEach(() => { @@ -10,8 +10,8 @@ beforeEach(() => { global.fetch = jest.fn(); }); -window.alert = jest.fn(); -const consoleSpy = jest.spyOn(console, 'log'); +jest.clearAllMocks(); +window.alert = jest.fn(); // Mock alert describe('DownloadPage Component', () => { test('renders correctly and shows initial UI elements', () => { @@ -21,122 +21,206 @@ describe('DownloadPage Component', () => { ); - // Check for initial input field and button - expect(screen.getByLabelText('Search for Package:')).toBeInTheDocument(); + // Check for input fields and buttons + expect(screen.getByText('Search by Name or Regex:')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('e.g., 1.2.3 or *')).toBeInTheDocument(); expect(screen.getByText('Search')).toBeInTheDocument(); }); - test('alerts if authentication token is missing on mount', () => { + test('updates input fields correctly on change', () => { render( ); - expect(consoleSpy).toHaveBeenCalledWith('no token set while entered download'); - consoleSpy.mockRestore(); + + const nameInput = screen.getByRole('textbox', { name: /search by name or regex:/i }); + const versionInput = screen.getByPlaceholderText('e.g., 1.2.3 or *'); + + fireEvent.change(nameInput, { target: { value: 'example-package' } }); + fireEvent.change(versionInput, { target: { value: '1.0.0' } }); + + expect(nameInput).toHaveValue('example-package'); + expect(versionInput).toHaveValue('1.0.0'); }); - test('performs search and displays results', async () => { - // Set the auth token in localStorage - localStorage.setItem('authToken', 'mockAuthToken'); + test('handles successful search with packages returned', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); - // Mock fetch response for search (fetch as jest.Mock).mockResolvedValueOnce({ status: 200, - json: async () => ({ - data: [ - { ID: '1', Name: 'example-package', Version: '1.0.0' }, - { ID: '2', Name: 'another-package', Version: '2.0.1' }, - ], - }), - headers: { - get: jest.fn(() => '10'), - }, + json: async () => [ + { ID: '1', Name: 'example-package', Version: '1.0.0' }, + { ID: '2', Name: 'another-package', Version: '2.0.0' }, + ], }); + + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); + + await waitFor(() => { + expect(screen.getByText('Available Packages:')).toBeInTheDocument(); + expect(screen.getByText('example-package')).toBeInTheDocument(); + expect(screen.getByText('another-package')).toBeInTheDocument(); + }); + }); + + test('handles no packages found during regex search', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); render( ); - await act(async () => { - // Enter a search term and submit the form - fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'example' } }); - fireEvent.click(screen.getByText('Search')); + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 404, }); - // Wait for the packages to appear + // search by name&version + // fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + // target: { value: 'nonexistent-package' }, + // }); + // fireEvent.click(screen.getByText('Search')); + // await waitFor(() => { + // expect(screen.getByText('Search failed with an unknown error.')).toBeInTheDocument(); + // }); + + // search by regex + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: '.*' }, + }); + fireEvent.click(screen.getByText('Search')); await waitFor(() => { - expect(screen.getByText('example-package')).toBeInTheDocument(); - expect(screen.getByText('another-package')).toBeInTheDocument(); + expect(screen.getByText('No packages found with the given regex.')).toBeInTheDocument(); }); }); - test('the case when response.status is 403', async () => { - localStorage.setItem('authToken', 'mockAuthToken'); + test('handles no packages found during name search', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); - // Mock fetch response with 403 error (fetch as jest.Mock).mockResolvedValueOnce({ - status: 403, - json: async () => ({ error: 'Authentication failed' }), + status: 404, }); + // search by name&version + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: 'nonexistent-package' }, + }); + fireEvent.click(screen.getByText('Search')); + await waitFor(() => { + expect(screen.getByText('Search failed with an unknown error.')).toBeInTheDocument(); + }); + }); + + test('handles error during search', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); render( ); - await act(async () => { - // Enter a search term and submit the form - fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'example' } }); - fireEvent.click(screen.getByText('Search')); + (fetch as jest.Mock).mockRejectedValueOnce(new Error('Network Error')); + + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: 'example-package' }, }); + fireEvent.click(screen.getByText('Search')); - // Wait for error message await waitFor(() => { - expect( - screen.getByText('Search failed: Authentication failed due to invalid or missing AuthenticationToken.') - ).toBeInTheDocument(); + expect(screen.getByText('An error occurred while searching for packages.')).toBeInTheDocument(); }); }); - // test('handles download button click', async () => { - // localStorage.setItem('authToken', 'mockAuthToken'); - - // // Mock fetch response for download - // (fetch as jest.Mock).mockResolvedValueOnce({ - // status: 200, - // json: async () => ({ - // data: { Content: 'base64content' }, - // metadata: { Name: 'example-package', Version: '1.0.0' }, - // }), - // }); - - // render( - // - // - // - // ); - - // await act(async () => { - // // Set state with mock data directly for simplicity - // fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'example' } }); - // fireEvent.click(screen.getByText('Search')); - // }); - - // // Mock the package display - // await waitFor(() => { - // expect(screen.getByText('example-package')).toBeInTheDocument(); - // }); - - // // Simulate click on download button - // const downloadButton = screen.getByText('Download'); - // fireEvent.click(downloadButton); - - // await waitFor(() => { - // expect(fetch).toHaveBeenCalledWith('/packages/1', expect.anything()); - // }); - // }); + test('handles package download success', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + const packages = [{ ID: '1', Name: 'example-package', Version: '1.0.0' }]; + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => packages, + }); + + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); + + await waitFor(() => { + expect(screen.getByText('example-package')).toBeInTheDocument(); + }); + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => ({ + data: { Content: 'mockBase64Content' }, + metadata: { Name: 'example-package', Version: '1.0.0' }, + }), + }); + + fireEvent.click(screen.getByText('Download')); + + await waitFor(() => { + expect(window.alert).not.toHaveBeenCalled(); + }); + }); + + test('handles package download failure', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + const packages = [{ ID: '1', Name: 'example-package', Version: '1.0.0' }]; + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => packages, + }); + + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); + + await waitFor(() => { + expect(screen.getByText('example-package')).toBeInTheDocument(); + }); + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 404, + }); + + fireEvent.click(screen.getByText('Download')); + + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith('Download failed: Package does not exist.'); + }); + }); }); diff --git a/frontend/test/history.test.tsx b/frontend/test/history.test.tsx new file mode 100644 index 0000000..195b01f --- /dev/null +++ b/frontend/test/history.test.tsx @@ -0,0 +1,181 @@ +import React from 'react'; +import '@testing-library/jest-dom'; +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; // Import MemoryRouter +import HistoryPage from '../src/pages/history'; + +beforeEach(() => { + localStorage.clear(); + jest.resetAllMocks(); + global.fetch = jest.fn(); +}); + +jest.spyOn(window, 'alert').mockImplementation(() => { }); // Mock alert + +describe('HistoryPage Component', () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + test('renders correctly and shows initial UI elements', () => { + render( + + + + ); + + // Check for the dropdown, search input, and button + expect(screen.getByText('View History')).toBeInTheDocument(); + expect(screen.getByText('Upload History')).toBeInTheDocument(); + expect(screen.getByText('Download History')).toBeInTheDocument(); + expect(screen.getByText('Search')).toBeInTheDocument(); + }); + + test('handles package search successfully', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + const mockPackages = [ + { ID: '1', Name: 'example-package-1' }, + { ID: '2', Name: 'example-package-2' }, + ]; + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => mockPackages, + }); + + + fireEvent.change(screen.getByRole('textbox'), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); + + await waitFor(() => { + expect(screen.getByText('example-package-1')).toBeInTheDocument(); + expect(screen.getByText('example-package-2')).toBeInTheDocument(); + }); + }); + + test('handles fetching upload history successfully', async () => { + // Set up mock localStorage + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + + render( + + + + ); + + const mockPackages = [ + { ID: '1', Name: 'example-package-1' }, + { ID: '2', Name: 'example-package-2' }, + { ID: '3', Name: 'example-package-3' }, + { ID: '4', Name: 'example-package-4' }, + ]; + + const mockHistory = [ + { User: 'user1', Date: '2023-12-01T10:00:00Z', Version: '1.0.0' }, + { User: 'user2', Date: '2023-12-02T12:00:00Z', Version: '1.1.0' }, + ]; + + // Mock the first API call for package search + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => mockPackages, + }); + + fireEvent.change(screen.getByRole('textbox'), { + target: { value: 'example-package' }, + }); + await act(async () => { + fireEvent.click(screen.getByText('Search')); + }); + + // Mock the second API call for fetching the history + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => mockHistory, + }); + + await act(async () => { + const viewHistoryButtons = screen.getAllByRole('button', { name: /View History/i }); + expect(viewHistoryButtons).toHaveLength(4); + fireEvent.click(viewHistoryButtons[1]); + }); + await (async () => { + expect(window.alert).not.toHaveBeenCalled(); + expect(screen.getByText('user1')).toBeInTheDocument(); + expect(screen.getByText('user2')).toBeInTheDocument(); + expect(screen.getByText('1.0.0')).toBeInTheDocument(); + expect(screen.getByText('1.1.0')).toBeInTheDocument(); + }); + }); + + test('displays error when search API fails', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + (fetch as jest.Mock).mockRejectedValueOnce(new Error('Network error')); + + fireEvent.change(screen.getByRole('textbox'), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); + + await waitFor(() => { + expect(screen.getByText('An error occurred while searching for packages.')).toBeInTheDocument(); + }); + }); + + test('displays error when fetching history fails', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + const mockPackages = [ + { ID: '1', Name: 'example-package-1' }, + { ID: '2', Name: 'example-package-2' }, + ]; + + // Mock the first API call for package search + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => mockPackages, + }); + + fireEvent.change(screen.getByRole('textbox'), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); + + await waitFor(() => { + expect(screen.getByText('example-package-1')).toBeInTheDocument(); + expect(screen.getByText('example-package-2')).toBeInTheDocument(); + }); + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 500, + }); + + const viewHistoryButtons = screen.getAllByText('View History'); // Gets all "View History" buttons + fireEvent.click(viewHistoryButtons[1]); // click first 1 + + + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith('Failed to fetch history. Please try again.'); + }); + }); +}); diff --git a/frontend/test/manageUser.test.tsx b/frontend/test/manageUser.test.tsx new file mode 100644 index 0000000..bc73b8d --- /dev/null +++ b/frontend/test/manageUser.test.tsx @@ -0,0 +1,208 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { MemoryRouter } from 'react-router-dom'; +import ManageUsers from '../src/pages/manageUsers'; // Adjust the path based on your file structure + +beforeEach(() => { + jest.resetAllMocks(); + localStorage.clear(); + global.fetch = jest.fn(); + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'adminUser'); +}); +jest.spyOn(window, 'alert').mockImplementation(() => { }); // Mock alert + +describe('ManageUsers Component', () => { + + test('renders the Create User form by default', () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + expect(screen.getByText('Manage Users')).toBeInTheDocument(); + expect(screen.getByText('Delete User')).toBeInTheDocument(); + expect(screen.getByText('Create User Group')).toBeInTheDocument(); + expect(screen.getByText('Username:')).toBeInTheDocument(); + expect(screen.getByText('Password:')).toBeInTheDocument(); + expect(screen.getByText('Permissions:')).toBeInTheDocument(); + expect(screen.getByText('Assign User Group:')).toBeInTheDocument(); + }); + + test('switches to Delete User mode and shows the delete form', () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + fireEvent.click(screen.getAllByText('Delete User')[0]); + + expect(screen.getAllByText('Delete User')[1]).toHaveStyle('background-color: #dc3545'); + expect(screen.getByText('Username to Delete:')).toBeInTheDocument(); + }); + + test('switches to Create User Group mode and shows the user group creation form', () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + fireEvent.click(screen.getAllByText('Create User Group')[0]); + + expect(screen.getAllByText('Create User Group')[1]).toHaveStyle('background-color: #28a745'); + expect(screen.getByText('Group Name:')).toBeInTheDocument(); + expect(screen.getByText('Description (Optional):')).toBeInTheDocument(); + }); + + test('handles creating a new user successfully', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + }); + + fireEvent.change(screen.getByLabelText('Username:'), { target: { value: 'newUser' } }); + fireEvent.change(screen.getByLabelText('Password:'), { target: { value: 'password123' } }); + fireEvent.click(screen.getAllByText('Create User')[1]); + + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith('New user "newUser" created successfully.'); + }); + }); + + test('handles deleting a user successfully', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + fireEvent.click(screen.getByText('Delete User')); + + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + }); + + fireEvent.change(screen.getByLabelText('Username to Delete:'), { target: { value: 'userToDelete' } }); + fireEvent.click(screen.getAllByText('Delete User')[1]); + + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith('User "userToDelete" deleted successfully.'); + }); + }); + + test('handles creating a new user group successfully', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + fireEvent.click(screen.getByText('Create User Group')); + + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + }); + + fireEvent.change(screen.getByLabelText('Group Name:'), { target: { value: 'newGroup' } }); + fireEvent.change(screen.getByLabelText('Description (Optional):'), { target: { value: 'A new user group' } }); + fireEvent.click(screen.getAllByText('Create User Group')[1]); + + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith('User group "newGroup" created successfully.'); + }); + }); + + test('handles error when creating a user fails', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + text: async () => 'User creation error', + }); + + fireEvent.change(screen.getByLabelText('Username:'), { target: { value: 'newUser' } }); + fireEvent.change(screen.getByLabelText('Password:'), { target: { value: 'password123' } }); + fireEvent.click(screen.getAllByText('Create User')[1]); + + await waitFor(() => { + expect(screen.getByText('Failed to create user: User creation error')).toBeInTheDocument(); + }); + }); + + test('handles error when deleting a user fails', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + fireEvent.click(screen.getByText('Delete User')); + + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + text: async () => 'User deletion error', + }); + + fireEvent.change(screen.getByLabelText('Username to Delete:'), { target: { value: 'userToDelete' } }); + fireEvent.click(screen.getAllByText('Delete User')[1]); + + await waitFor(() => { + expect(screen.getByText('Failed to delete user: User deletion error')).toBeInTheDocument(); + }); + }); + + test('handles error when creating a user group fails', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + fireEvent.click(screen.getByText('Create User Group')); + + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + text: async () => 'User group creation error', + }); + + fireEvent.change(screen.getByLabelText('Group Name:'), { target: { value: 'newGroup' } }); + fireEvent.change(screen.getByLabelText('Description (Optional):'), { target: { value: 'A new user group' } }); + fireEvent.click(screen.getAllByText('Create User Group')[1]); + + await waitFor(() => { + expect(screen.getByText('Failed to create user group: User group creation error')).toBeInTheDocument(); + }); + }); + +}); diff --git a/frontend/test/rating.test.tsx b/frontend/test/rating.test.tsx index ef10e21..412548e 100644 --- a/frontend/test/rating.test.tsx +++ b/frontend/test/rating.test.tsx @@ -1,56 +1,122 @@ import React from 'react'; +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; import '@testing-library/jest-dom'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; -import RatingPage from '../src/pages/rating'; +import RatingPage from '../src/pages/rating'; // Adjust the path to your actual file beforeEach(() => { - localStorage.clear(); jest.resetAllMocks(); + localStorage.clear(); + localStorage.setItem('authToken', 'mockAuthToken123'); global.fetch = jest.fn(); }); - -window.alert = jest.fn(); -const consoleSpy = jest.spyOn(console, 'log'); +jest.spyOn(window, 'alert').mockImplementation(() => { }); // Mock alert describe('RatingPage Component', () => { - test('renders correctly and shows initial UI elements', () => { + + test('renders correctly and performs package search', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + const mockPackages = [ + { ID: '1', Name: 'example-package-1', Version: '1.0.0' }, + { ID: '2', Name: 'example-package-2', Version: '2.0.0' }, + ]; + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => mockPackages, + }); + render( ); - // Check for input field and button - expect(screen.getByLabelText('Search for Package:')).toBeInTheDocument(); - expect(screen.getByText('Search')).toBeInTheDocument(); + // Simulate user input and search + await act(async () => { + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); + }); + + // Assert search results + await waitFor(() => { + expect(screen.getByText('example-package-1')).toBeInTheDocument(); + expect(screen.getByText('example-package-2')).toBeInTheDocument(); + }); }); - test('alerts if authentication token is missing on mount', () => { + test('fetches and displays package rating data', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + const mockPackages = [ + { ID: '1', Name: 'example-package-1', Version: '1.0.0' }, + ]; + + const mockRatingData = { + BusFactor: '0.8', + BusFactorLatency: '0.01', + Correctness: '0.9', + CorrectnessLatency: '0.02', + RampUp: '0.7', + RampUpLatency: '0.02', + ResponsiveMaintainer: '1.0', + ResponsiveMaintainerLatency: '0.03', + LicenseScore: '0.85', + LicenseScoreLatency: '0.01', + GoodPinningPractice: '0.9', + GoodPinningPracticeLatency: '0.01', + PullRequest: '0.7', + PullRequestLatency: '0.02', + NetScore: '0.88', + NetScoreLatency: '0.01', + }; + + // Mock package search response + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => mockPackages, + }); + render( ); - // Check console message - expect(consoleSpy).toHaveBeenCalledWith('no token set while entered rating'); - consoleSpy.mockRestore(); - }); + // Simulate user input and search + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); - test('performs search and displays results', async () => { - // Set the auth token in localStorage - localStorage.setItem('authToken', 'mockAuthToken'); + await waitFor(() => { + expect(screen.getByText('example-package-1')).toBeInTheDocument(); + }); - // Mock fetch response for search + // Mock rating API response (fetch as jest.Mock).mockResolvedValueOnce({ status: 200, - json: async () => ({ - data: [ - { ID: '1', Name: 'example-package', Version: '1.0.0' }, - { ID: '2', Name: 'another-package', Version: '2.0.1' }, - ], - }), + json: async () => mockRatingData, + }); + + fireEvent.click(screen.getByText('Get Rating')); + + // Assert rating data is displayed + await waitFor(() => { + expect(screen.getByText('BusFactor:')).toBeInTheDocument(); + expect(screen.getByText('Correctness:')).toBeInTheDocument(); + expect(screen.getByText('RampUp:')).toBeInTheDocument(); + }); + }); + + test('shows error if search with name fails 404', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 404, }); render( @@ -59,36 +125,25 @@ describe('RatingPage Component', () => { ); - // Enter a search term and submit the form - fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'example' } }); - fireEvent.click(screen.getByText('Search')); + await act(async () => { + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: 'nonexistent-package' }, + }); + fireEvent.click(screen.getByText('Search')); + }); + - // Wait for the packages to appear await waitFor(() => { - expect(screen.getByText('example-package')).toBeInTheDocument(); - expect(screen.getByText('another-package')).toBeInTheDocument(); + expect(screen.getByText('No package found.')).toBeInTheDocument(); }); }); - test('fetches and displays rating data for a package', async () => { - localStorage.setItem('authToken', 'mockAuthToken'); - - // Mock fetch response for search - (fetch as jest.Mock) - .mockResolvedValueOnce({ - status: 200, - json: async () => ({ - data: [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], - }), - }) - .mockResolvedValueOnce({ - status: 200, - json: async () => ({ - overall: 4.5, - dependencyPinning: 4.0, - codeReviewMetric: 4.2, - }), - }); + test('shows error if search with regex fails 404', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 404, + }); render( @@ -96,43 +151,51 @@ describe('RatingPage Component', () => { ); - // Enter a search term and submit the form - fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'example' } }); - fireEvent.click(screen.getByText('Search')); + await act(async () => { + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: '.*' }, + }); + fireEvent.click(screen.getByText('Search')); + }); + - // Wait for search results await waitFor(() => { - expect(screen.getByText('example-package')).toBeInTheDocument(); + expect(screen.getByText('No packages found with the given regex.')).toBeInTheDocument(); + }); + }); + + test('shows error if search with name fails 500', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 500, + }); + + render( + + + + ); + + await act(async () => { + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: 'nonexistent-package' }, + }); + fireEvent.click(screen.getByText('Search')); }); - // Click on "Get Rating" button - fireEvent.click(screen.getByText('Get Rating')); - // Wait for rating data to appear await waitFor(() => { - expect(screen.getByText('Rating Details')).toBeInTheDocument(); - expect(screen.getByText('Overall Rating:')).toBeInTheDocument(); - expect(screen.getByText('4.5 / 5')).toBeInTheDocument(); + expect(screen.getByText('Search failed with an unknown error.')).toBeInTheDocument(); }); }); - test('handles pagination: next and previous pages', async () => { - localStorage.setItem('authToken', 'mockAuthToken'); - - // Mock fetch response for pagination - (fetch as jest.Mock) - .mockResolvedValueOnce({ - status: 200, - json: async () => ({ - data: [{ ID: '1', Name: 'package-page-1', Version: '1.0.0' }], - }), - }) - .mockResolvedValueOnce({ - status: 200, - json: async () => ({ - data: [{ ID: '2', Name: 'package-page-2', Version: '2.0.1' }], - }), - }); + test('shows error if search with regex fails 500', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 500, + }); render( @@ -140,29 +203,58 @@ describe('RatingPage Component', () => { ); - // First search - fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'package' } }); - fireEvent.click(screen.getByText('Search')); + await act(async () => { + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: '.*' }, + }); + fireEvent.click(screen.getByText('Search')); + }); + - // Wait for first page result await waitFor(() => { - expect(screen.getByText('package-page-1')).toBeInTheDocument(); + expect(screen.getByText('Search failed with an unknown error.')).toBeInTheDocument(); }); + }); - // Click "Next Page" - fireEvent.click(screen.getByText('Next Page')); + test('shows error if rating fetch fails', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + const mockPackages = [ + { ID: '1', Name: 'example-package-1', Version: '1.0.0' }, + ]; + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => mockPackages, + }); + + render( + + + + ); + await act(async () => { + fireEvent.change(screen.getByRole('textbox', { name: /search by name or regex:/i }), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); + }); - // Wait for second page result await waitFor(() => { - expect(screen.getByText('package-page-2')).toBeInTheDocument(); + expect(screen.getByText('example-package-1')).toBeInTheDocument(); + }); + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 500, + }); + + await act(async () => { + fireEvent.click(screen.getByText('Get Rating')); }); - // Click "Previous Page" - fireEvent.click(screen.getByText('Previous Page')); - // Wait for first page result again await waitFor(() => { - expect(screen.getByText('package-page-1')).toBeInTheDocument(); + expect(window.alert).toHaveBeenCalledWith('The package rating system choked on at least one of the metrics.'); }); }); }); diff --git a/frontend/test/update.test.tsx b/frontend/test/update.test.tsx index 3621825..2548668 100644 --- a/frontend/test/update.test.tsx +++ b/frontend/test/update.test.tsx @@ -1,53 +1,73 @@ import React from 'react'; +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; import '@testing-library/jest-dom'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; -import UpdatePage from '../src/pages/update'; +import UpdatePage from '../src/pages/update'; // Adjust the path if necessary beforeEach(() => { - localStorage.clear(); jest.resetAllMocks(); + localStorage.clear(); global.fetch = jest.fn(); + localStorage.setItem('authToken', 'mockAuthToken123'); }); - -window.alert = jest.fn(); -const consoleSpy = jest.spyOn(console, 'log'); +jest.spyOn(window, 'alert').mockImplementation(() => {}); // Mock alert describe('UpdatePage Component', () => { - test('renders correctly and shows initial UI elements', () => { + test('renders the search form initially', () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); render( ); - // Check for initial input field and button - expect(screen.getByLabelText('Search for Package:')).toBeInTheDocument(); + expect(screen.getByText('Update a Package')).toBeInTheDocument(); + expect(screen.getByLabelText('Search by Name or Regex:')).toBeInTheDocument(); expect(screen.getByText('Search')).toBeInTheDocument(); }); - test('alerts if authentication token is missing on mount', () => { + test('handles searching for packages successfully', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + const mockPackages = [ + { ID: 1, Name: 'example-package-1', Version: '1.0.0' }, + { ID: 2, Name: 'example-package-2', Version: '1.2.0' }, + ]; + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => mockPackages, + }); + render( ); - expect(consoleSpy).toHaveBeenCalledWith('no token set while entered update'); - consoleSpy.mockRestore(); + + fireEvent.change(screen.getByLabelText('Search by Name or Regex:'), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); + + await waitFor(() => { + expect(screen.getByText('Search Results:')).toBeInTheDocument(); + expect(screen.getByText('example-package-1')).toBeInTheDocument(); + expect(screen.getByText('example-package-2')).toBeInTheDocument(); + }); }); - test('performs search and displays results', async () => { - localStorage.setItem('authToken', 'mockAuthToken'); - - // Mock fetch response for search + test('handles selecting a package', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + const mockPackages = [ + { ID: 1, Name: 'example-package-1', Version: '1.0.0' }, + ]; + (fetch as jest.Mock).mockResolvedValueOnce({ status: 200, - json: async () => ({ - data: [ - { ID: '1', Name: 'example-package', Version: '1.0.0' }, - { ID: '2', Name: 'another-package', Version: '2.0.1' }, - ], - }), + json: async () => mockPackages, }); render( @@ -56,40 +76,31 @@ describe('UpdatePage Component', () => { ); - // Enter a search term and submit the form - fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'example' } }); + fireEvent.change(screen.getByLabelText('Search by Name or Regex:'), { + target: { value: 'example-package' }, + }); fireEvent.click(screen.getByText('Search')); - // Wait for the packages to appear await waitFor(() => { - expect(screen.getByText('example-package')).toBeInTheDocument(); - expect(screen.getByText('another-package')).toBeInTheDocument(); + fireEvent.click(screen.getByText('Select')); }); + + expect(screen.getByLabelText('Package Name (Pre-filled):')).toHaveValue('example-package-1'); + expect(screen.getByLabelText('Current Version (Pre-filled):')).toHaveValue('1.0.0'); + expect(screen.getByLabelText('New Version:')).toBeInTheDocument(); }); - test('handles pagination: next and previous pages', async () => { - localStorage.setItem('authToken', 'mockAuthToken'); - - // Mock fetch response for search with pagination - (fetch as jest.Mock) - .mockResolvedValueOnce({ - status: 200, - json: async () => ({ - data: [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], - }), - }) - .mockResolvedValueOnce({ - status: 200, - json: async () => ({ - data: [{ ID: '2', Name: 'another-package', Version: '2.0.1' }], - }), - }) - .mockResolvedValueOnce({ - status: 200, - json: async () => ({ - data: [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], - }), - }); + test('handles providing a file for upload', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + const mockPackages = [ + { ID: 1, Name: 'example-package-1', Version: '1.0.0' }, + ]; + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => mockPackages, + }); render( @@ -97,36 +108,114 @@ describe('UpdatePage Component', () => { ); - // Initial search - fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'example' } }); + fireEvent.change(screen.getByLabelText('Search by Name or Regex:'), { + target: { value: 'example-package' }, + }); fireEvent.click(screen.getByText('Search')); await waitFor(() => { - expect(screen.getByText('example-package')).toBeInTheDocument(); + fireEvent.click(screen.getByText('Select')); }); - // Go to next page - fireEvent.click(screen.getByText('Next Page')); + const file = new File(['dummy content'], 'example.zip', { type: 'application/zip' }); + const fileInput = screen.getByLabelText('Upload New Version File:') as HTMLInputElement; + fireEvent.change(fileInput, { target: { files: [file] } }); + + expect(fileInput.files![0]).toBe(file); + expect(fileInput.files!.length).toBe(1); + }); + + test('handles providing a URL for update', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + const mockPackages = [ + { ID: 1, Name: 'example-package-1', Version: '1.0.0' }, + ]; + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => mockPackages, + }); + + render( + + + + ); + + fireEvent.change(screen.getByLabelText('Search by Name or Regex:'), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); + await waitFor(() => { - expect(screen.getByText('another-package')).toBeInTheDocument(); + fireEvent.click(screen.getByText('Select')); + }); + + const urlInput = screen.getByLabelText('Or Provide URL for the Update:'); + fireEvent.change(urlInput, { target: { value: 'https://example.com/package.zip' } }); + + expect(urlInput).toHaveValue('https://example.com/package.zip'); + }); + + test('handles updating a package successfully', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + const mockPackages = [ + { ID: 1, Name: 'example-package-1', Version: '1.0.0' }, + ]; + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + json: async () => mockPackages, }); - // Go back to previous page - fireEvent.click(screen.getByText('Previous Page')); + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 200, + }); + + render( + + + + ); + + fireEvent.change(screen.getByLabelText('Search by Name or Regex:'), { + target: { value: 'example-package' }, + }); + fireEvent.click(screen.getByText('Search')); + + await waitFor(() => { + fireEvent.click(screen.getByText('Select')); + }); + + fireEvent.change(screen.getByLabelText('New Version:'), { target: { value: '2.0.0' } }); + + const urlInput = screen.getByLabelText('Or Provide URL for the Update:'); + fireEvent.change(urlInput, { target: { value: 'https://example.com/package.zip' } }); + + fireEvent.click(screen.getByText('Update Package')); + await waitFor(() => { - expect(screen.getByText('example-package')).toBeInTheDocument(); + expect(window.alert).toHaveBeenCalledWith('Version updated successfully!'); }); }); - test('selects a package and displays the update form', async () => { - localStorage.setItem('authToken', 'mockAuthToken'); + test('handles error when updating a package fails', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + const mockPackages = [ + { ID: 1, Name: 'example-package-1', Version: '1.0.0' }, + ]; - // Mock fetch response for search (fetch as jest.Mock).mockResolvedValueOnce({ status: 200, - json: async () => ({ - data: [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], - }), + json: async () => mockPackages, + }); + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 400, + text: async () => 'Error message', }); render( @@ -135,17 +224,24 @@ describe('UpdatePage Component', () => { ); - // Perform search and select a package - fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'example' } }); + fireEvent.change(screen.getByLabelText('Search by Name or Regex:'), { + target: { value: 'example-package' }, + }); fireEvent.click(screen.getByText('Search')); await waitFor(() => { - screen.getByText('example-package'); + fireEvent.click(screen.getByText('Select')); }); - // Select package to display update form - fireEvent.click(screen.getByText('Select')); - expect(screen.getByText('Package Name (Pre-filled):')).toBeInTheDocument(); - expect(screen.getByText('Current Version (Pre-filled):')).toBeInTheDocument(); + fireEvent.change(screen.getByLabelText('New Version:'), { target: { value: '2.0.0' } }); + + const urlInput = screen.getByLabelText('Or Provide URL for the Update:'); + fireEvent.change(urlInput, { target: { value: 'https://example.com/package.zip' } }); + + fireEvent.click(screen.getByText('Update Package')); + + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith('Error updating package: Error message'); + }); }); }); diff --git a/frontend/test/upload.test.tsx b/frontend/test/upload.test.tsx index 4a1a536..dab7bb4 100644 --- a/frontend/test/upload.test.tsx +++ b/frontend/test/upload.test.tsx @@ -1,165 +1,155 @@ import React from 'react'; +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; import '@testing-library/jest-dom'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; -import UpdatePage from '../src/pages/update'; +import UploadPage from '../src/pages/upload'; // Adjust the path if necessary beforeEach(() => { - localStorage.clear(); jest.resetAllMocks(); + localStorage.clear(); global.fetch = jest.fn(); + localStorage.setItem('authToken', 'mockAuthToken123'); }); +jest.spyOn(window, 'alert').mockImplementation(() => {}); // Mock alert -window.alert = jest.fn(); -const consoleSpy = jest.spyOn(console, 'log'); - -describe('UpdatePage Component', () => { - test('renders correctly and shows initial UI elements', () => { +describe('UploadPage Component', () => { + test('renders the upload form', () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); render( - + ); - expect(screen.getByLabelText('Search for Package:')).toBeInTheDocument(); - expect(screen.getByText('Search')).toBeInTheDocument(); + expect(screen.getByText('Upload a Package')).toBeInTheDocument(); + expect(screen.getByLabelText('Package Name:')).toBeInTheDocument(); + expect(screen.getByLabelText('Upload ZIP File:')).toBeInTheDocument(); + expect(screen.getByLabelText('Or Provide URL for the Package:')).toBeInTheDocument(); + expect(screen.getByText('Upload Package')).toBeInTheDocument(); }); - test('alerts if authentication token is missing on mount', () => { + test('handles uploading a ZIP file successfully', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 201, + json: async () => [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], + }); + render( - + ); - expect(consoleSpy).toHaveBeenCalledWith('no token set while entered update'); - consoleSpy.mockRestore(); + + fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); + + const file = new File(['dummy content'], 'example.zip', { type: 'application/zip' }); + const fileInput = screen.getByLabelText('Upload ZIP File:') as HTMLInputElement; + fireEvent.change(fileInput, { target: { files: [file] } }); + + expect(fileInput.files![0]).toBe(file); + expect(fileInput.files!.length).toBe(1); + + fireEvent.click(screen.getByText('Upload Package')); + + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith('Package uploaded successfully!'); + }); }); - test('performs search and displays results', async () => { - localStorage.setItem('authToken', 'mockAuthToken'); - - // Mock fetch response for search + test('handles providing a URL for upload successfully', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); (fetch as jest.Mock).mockResolvedValueOnce({ - status: 200, - json: async () => ({ - data: [ - { Name: 'example-package', Version: '1.0.0', ID: '1' }, - { Name: 'another-package', Version: '2.0.1', ID: '2' }, - ], - }), - headers: { - get: jest.fn((header) => (header === 'offset' ? '10' : null)), // Mock offset header for pagination - }, + status: 201, + json: async () => [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], }); render( - + ); - // Enter a search term and submit the form - fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'example' } }); - fireEvent.click(screen.getByText('Search')); + fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); + fireEvent.change(screen.getByLabelText('Or Provide URL for the Package:'), { + target: { value: 'https://example.com/package.zip' }, + }); + + fireEvent.click(screen.getByText('Upload Package')); - // Wait for the packages to appear await waitFor(() => { - expect(screen.getByText('example-package')).toBeInTheDocument(); - expect(screen.getByText('another-package')).toBeInTheDocument(); + expect(window.alert).toHaveBeenCalledWith('Package uploaded successfully!'); }); }); - test('handles pagination: next and previous pages', async () => { - localStorage.setItem('authToken', 'mockAuthToken'); - - // Mock fetch response for search with pagination - (fetch as jest.Mock) - .mockResolvedValueOnce({ - status: 200, - json: async () => ({ - data: [{ Name: 'example-package', Version: '1.0.0', ID: '1' }], - }), - headers: { - get: jest.fn((header) => (header === 'offset' ? '10' : null)), // Initial page with offset - }, - }) - .mockResolvedValueOnce({ - status: 200, - json: async () => ({ - data: [{ Name: 'another-package', Version: '2.0.1', ID: '2' }], - }), - headers: { - get: jest.fn((header) => (header === 'offset' ? '20' : null)), // Next page with offset - }, - }) - .mockResolvedValueOnce({ - status: 200, - json: async () => ({ - data: [{ Name: 'example-package', Version: '1.0.0', ID: '1' }], - }), - headers: { - get: jest.fn((header) => (header === 'offset' ? '10' : null)), // Initial page with offset - }, - }); - + test('displays error when neither ZIP file nor URL is provided', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); render( - + ); - // Initial search - fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'example' } }); - fireEvent.click(screen.getByText('Search')); + fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); + + fireEvent.click(screen.getByText('Upload Package')); await waitFor(() => { - expect(screen.getByText('example-package')).toBeInTheDocument(); + expect(window.alert).toHaveBeenCalledWith('Please provide either a ZIP file or a URL for the upload.'); }); + }); + + test('displays error for non-ZIP file uploads', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + render( + + + + ); + + fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); + + const file = new File(['dummy content'], 'example.txt', { type: 'text/plain' }); + const fileInput = screen.getByLabelText('Upload ZIP File:') as HTMLInputElement; - // Go to next page - fireEvent.click(screen.getByText('Next Page')); await waitFor(() => { - expect(screen.getByText('another-package')).toBeInTheDocument(); + fireEvent.change(fileInput, { target: { files: [file] } }); + expect(window.alert).toHaveBeenCalledWith('Please select a ZIP file.'); }); - - // Go back to previous page - fireEvent.click(screen.getByText('Previous Page')); await waitFor(() => { - expect(screen.getByText('example-package')).toBeInTheDocument(); + fireEvent.click(screen.getByText('Upload Package')); + expect(window.alert).toHaveBeenCalledWith('Please provide either a ZIP file or a URL for the upload.'); }); - }); - test('selects a package and displays the update form', async () => { - localStorage.setItem('authToken', 'mockAuthToken'); + }); - // Mock fetch response for search + test('handles error when upload fails', async () => { + localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); (fetch as jest.Mock).mockResolvedValueOnce({ - status: 200, - json: async () => ({ - data: [{ Name: 'example-package', Version: '1.0.0', ID: '1' }], - }), - headers: { - get: jest.fn((header) => (header === 'offset' ? '10' : null)), // Mock offset header - }, + status: 400, }); render( - + ); - // Perform search and select a package - fireEvent.change(screen.getByLabelText('Search for Package:'), { target: { value: 'example' } }); - fireEvent.click(screen.getByText('Search')); + fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); + fireEvent.change(screen.getByLabelText('Or Provide URL for the Package:'), { + target: { value: 'https://example.com/package.zip' }, + }); + + fireEvent.click(screen.getByText('Upload Package')); await waitFor(() => { - screen.getByText('example-package'); + expect(window.alert).toHaveBeenCalledWith('Upload failed: Missing fields or invalid data.'); }); - - // Select package to display update form - fireEvent.click(screen.getByText('Select')); - expect(screen.getByText('Package Name (Pre-filled):')).toBeInTheDocument(); - expect(screen.getByText('Current Version (Pre-filled):')).toBeInTheDocument(); }); }); From eaebbbdae15432495675b2e9519310e55d895821 Mon Sep 17 00:00:00 2001 From: te99y Date: Sat, 7 Dec 2024 10:50:14 -0500 Subject: [PATCH 2/9] changed alert to setError --- frontend/src/pages/download.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/download.tsx b/frontend/src/pages/download.tsx index e4fba9f..64c818e 100644 --- a/frontend/src/pages/download.tsx +++ b/frontend/src/pages/download.tsx @@ -62,7 +62,7 @@ const DownloadPage: React.FC = () => { setSearchPerformed(true); } else if (response.status === 404) { setPackages([]); - alert('No packages found with the given regex.'); + setError('No packages found with the given regex.'); } else { setError('Search failed with an unknown error.'); } From d6b1eb7aefbf8a5baad96911d93046acc9d06fb9 Mon Sep 17 00:00:00 2001 From: te99y Date: Sat, 7 Dec 2024 10:53:21 -0500 Subject: [PATCH 3/9] adapt to interface change in backend --- frontend/src/pages/update.tsx | 46 ++++++++++++++++------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/frontend/src/pages/update.tsx b/frontend/src/pages/update.tsx index ae236b8..ce8d235 100644 --- a/frontend/src/pages/update.tsx +++ b/frontend/src/pages/update.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import PageLayout from './pageLayout'; interface PackageMetadata { - ID: string; + ID: number; // Ensure ID is a number Name: string; Version: string; } @@ -18,20 +18,16 @@ const UpdatePage: React.FC = () => { const [url, setUrl] = useState(''); // URL for URL-based updates const [authToken, setAuthToken] = useState(null); const [error, setError] = useState(null); - - // Pagination state const [offset, setOffset] = useState(0); const [hasNextPage, setHasNextPage] = useState(false); - const [searchPerformed, setSearchPerformed] = useState(false); - // Retrieve auth token from localStorage on component mount useEffect(() => { const token = localStorage.getItem('authToken'); if (token) { setAuthToken(token); } else { - console.log('No token set while entering update'); + console.log('No token set while entering update page.'); } }, []); @@ -65,17 +61,16 @@ const UpdatePage: React.FC = () => { if (response.status === 200) { const data: PackageMetadata[] = await response.json(); setPackages(data); - setHasNextPage(false); // Pagination is not applicable for regex searches + setHasNextPage(false); setSearchPerformed(true); } else if (response.status === 404) { - setPackages([]); alert('No packages found with the given regex.'); } else { setError('Search failed with an unknown error.'); } } else { const requestBody = [ - { Name: searchTerm, Version: versionTerm ? versionTerm : '*' }, + { Name: searchTerm, Version: versionTerm || '*' }, ]; const response = await fetch(`/packages?offset=${pageOffset}`, { method: 'POST', @@ -121,7 +116,7 @@ const UpdatePage: React.FC = () => { const isZipFile = selectedFile.type === 'application/zip' || selectedFile.name.endsWith('.zip'); if (!isZipFile) { - alert('Please select a ZIP file.'); + alert('Please select a valid ZIP file.'); e.target.value = ''; setFile(null); } else { @@ -144,14 +139,23 @@ const UpdatePage: React.FC = () => { return; } + if (file && url) { + alert('Please provide only one of either a ZIP file or a URL for the update.'); + return; + } + try { + console.log(`name : ${selectedPackage.Name}`); let payload: any = { metadata: { Name: selectedPackage.Name, Version: newVersion, - ID: selectedPackage.ID, + ID: Number(selectedPackage.ID), + }, + data: { + Name: selectedPackage.Name, + debloat: debloat, }, - data: {}, }; if (file) { @@ -159,24 +163,16 @@ const UpdatePage: React.FC = () => { const reader = new FileReader(); reader.onload = () => { if (typeof reader.result === 'string') { - resolve(reader.result.split(',')[1]); + resolve(reader.result.split(',')[1]); // Base64 encode } }; reader.onerror = (error) => reject(error); reader.readAsDataURL(file); }); - payload.data = { - Content: fileContent, - Name: selectedPackage.Name, - debloat: debloat, - }; + payload.data.Content = fileContent; } else if (url) { - payload.data = { - URL: url, - Name: selectedPackage.Name, - debloat: debloat, - }; + payload.data.URL = url; } const response = await fetch(`/package/${selectedPackage.ID}`, { @@ -191,14 +187,14 @@ const UpdatePage: React.FC = () => { if (response.status === 200) { alert('Version updated successfully!'); } else { - alert(await response.text()); + const errorMsg = await response.text(); + alert(`Error updating package: ${errorMsg}`); } } catch (error) { console.error('Error updating package:', error); alert('An error occurred while updating the package.'); } }; - return (
{ e.preventDefault(); handleSearch(0); }} style={{ maxWidth: '500px', margin: '0 auto' }}> From f1ee3fa306e61899796043946018f90c4648e6f8 Mon Sep 17 00:00:00 2001 From: te99y Date: Sat, 7 Dec 2024 10:54:46 -0500 Subject: [PATCH 4/9] Prevent caching to avoid res.status 304 instead of 200 --- frontend/src/pages/history.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/pages/history.tsx b/frontend/src/pages/history.tsx index 5f1eb5f..24c4383 100644 --- a/frontend/src/pages/history.tsx +++ b/frontend/src/pages/history.tsx @@ -118,12 +118,14 @@ const HistoryPage: React.FC = () => { method: 'GET', headers: { 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache', // Prevent caching so we dont get 304 but 200 'X-Authorization': authToken, }, }); if (response.ok) { const data: HistoryEntry[] = await response.json(); + // console.log('Fetched history:', data); // delete this setHistory(data); } else { alert('Failed to fetch history. Please try again.'); From da2d0ac335afa9b146a91221a7876585a4f43cc3 Mon Sep 17 00:00:00 2001 From: te99y Date: Sat, 7 Dec 2024 10:55:35 -0500 Subject: [PATCH 5/9] Add res.status 404 --- frontend/src/pages/rating.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/rating.tsx b/frontend/src/pages/rating.tsx index 90fd4d7..4979171 100644 --- a/frontend/src/pages/rating.tsx +++ b/frontend/src/pages/rating.tsx @@ -82,7 +82,7 @@ const RatingPage: React.FC = () => { setSearchPerformed(true); } else if (response.status === 404) { setPackages([]); - alert('No packages found with the given regex.'); + setError('No packages found with the given regex.'); } else { setError('Search failed with an unknown error.'); } @@ -107,6 +107,8 @@ const RatingPage: React.FC = () => { setOffset(pageOffset); setHasNextPage(data.length > 0); setSearchPerformed(true); + } else if (response.status === 404) { + setError('No package found.') } else { setError('Search failed with an unknown error.'); } From c086c71ee4057fadb2ca12c694cb98a48f58b539 Mon Sep 17 00:00:00 2001 From: te99y Date: Sat, 7 Dec 2024 10:56:44 -0500 Subject: [PATCH 6/9] Add htmlFor tag for writing unit test --- frontend/src/pages/manageUsers.tsx | 48 +++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/frontend/src/pages/manageUsers.tsx b/frontend/src/pages/manageUsers.tsx index 17e9704..d1baff4 100644 --- a/frontend/src/pages/manageUsers.tsx +++ b/frontend/src/pages/manageUsers.tsx @@ -246,8 +246,11 @@ const ManageUsers: React.FC = () => {
e.preventDefault()} style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
- + setNewUsername(e.target.value)} @@ -256,8 +259,11 @@ const ManageUsers: React.FC = () => { />
- + setNewPassword(e.target.value)} @@ -336,16 +342,27 @@ const ManageUsers: React.FC = () => {
)} - {currentMode === Modes.DELETE_USER && ( -
+
e.preventDefault()} style={{ display: 'flex', flexDirection: 'column', gap: '15px' }} >
- + setUsernameToDelete(e.target.value)} @@ -373,14 +390,26 @@ const ManageUsers: React.FC = () => { )} {currentMode === Modes.CREATE_USER_GROUP && ( -
+
e.preventDefault()} style={{ display: 'flex', flexDirection: 'column', gap: '15px' }} >
- + setNewGroupName(e.target.value)} @@ -389,8 +418,11 @@ const ManageUsers: React.FC = () => { />
- + setNewGroupDescription(e.target.value)} From b1216a71f4360485e12210882089d94418f89d55 Mon Sep 17 00:00:00 2001 From: te99y Date: Sun, 8 Dec 2024 20:16:47 -0500 Subject: [PATCH 7/9] make test time shorter --- playwright.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 7d04c96..cdac38f 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -8,8 +8,8 @@ import { defineConfig } from '@playwright/test'; * > npm playwright test e2e/someTestFile.spec.ts*/ export default defineConfig({ testDir: 'e2e', - timeout: 20 * 1000, - retries: 1, + timeout: 10 * 1000, + retries: 0, use: { baseURL: 'http://localhost:4000', // our projecy url headless: true, // don't show browser From 3b43b1aa979c453906a86d2c44190a5fbdeb6ac1 Mon Sep 17 00:00:00 2001 From: te99y Date: Sun, 8 Dec 2024 20:18:31 -0500 Subject: [PATCH 8/9] test for package upload & download --- e2e/assets/test-package.zip | Bin 0 -> 2148 bytes e2e/packageIO.spec.ts | 69 ++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 e2e/assets/test-package.zip diff --git a/e2e/assets/test-package.zip b/e2e/assets/test-package.zip new file mode 100644 index 0000000000000000000000000000000000000000..3553e6ce7e033dd41535686b737a3d7b4f7c5540 GIT binary patch literal 2148 zcmZ`)c{tSD8~>8+B1_E3WwM2c30Xq(m7)<1vSyepBPy~pWVw;X7GtkWDO+xqTNq@S z$&w;Fm1Q!GNUq7wq=Y-}uWsF*-+j;Xob#OXyr1{{@qRv^*V1eUhbRE>0KmjqyaVt< zu$}-vSFD$-=h*|6U$b}sx(u36NJG`@3OfKC;Q#=^AF@uKIA&~aZHzh9oHDUbv~d|y z1a_O)F+rVC1$DH9%1H&HT!lX|ZAY36b>^}2c%f&=RBY(H?aR_jszc{qyF`*=Gsl?~ z^|iX=oePk16xW#BfaohPm8Ok{R~F^EH0#=Kx=*B}_WWTlR=~Fh?p9Muj-X%E0!J#E zNa!Rr*BjlRJ@^WN2?M_>FaAB_>DQKf9yh!2)C%rWC>$|{f8jW+h=Gq=0_f+lm zgI9_fm9$By#G-sR^uUJ5;!?1Pc)3ITayz*fO2g!|B3!H0%YE7kNNaPB(6`v~di9S(hj??-*ACYDn-DjyvZ+l{$Ld7Ysu6zFeO(++O(?$5_SHDZZX z!o(qG>cxX$tBwMi2)V?%B`}ULZ|<~rFme^%6ViH(P2D@u;6rMFJi)@%p(VUVo5P^~ z!@ejvqh9FLuFY!V^=ITr6Q9s0WEh>vDCxDk)m=$aYNULJESp>N&?JHGHQmp5edBnsOhp)&Zli$E)Uos7t9~5Pjk4wM>BDp-1 z?zHMJN&TCzcKq!#_t^k|&f+VJ>;JI&E4$$r0)2gwZJK?kYW(E&{XDlFl@QuZT>M@F zuu<6P7#`-H1WVQ=cHh14_lOB%h+jGWC5MMVu=Z`3H~ftq3k zl#r=`i2BTzmRCpHT6zfvR^Dbi;VK{^YAoWnF(dxVWN*sh!@i-V_kiWs!)6ad` zwKi9KpWUB4tuh(le`eCd)bdeQ?-&7DVPPBHXPm%{uh!q^J8Wu;=h6mq}7mDFvan2XbJeu~_ zs>UZ-WLn#WvaY8U`s!QobZ{m|6P|4)0ckV%4ZYZ~u+H=1?S|mLTl;!digAf$MR-}RQ4dv`!4E|pP;d@POU3D&78UOq%6+3nQ{au~+Y1~rmH5^j?{kiNGb$oKT7t%>f zV7=mrT)BuK*SUSso3<5)PA7oOUqU(q9ik@V`xUD#GJX7Zo3!6+@!K^wl|SCwr;rZo zJXavMP#CqcHR5!~1shlRwwObL-6WP13mRJ7Z&pm<>$X}c{3IKgud&2vxJey}n{(9q z-TcpER4C+eF}}!GKjc0_aHYbTGSSbkhcDIbx8g=qnpOFnZi{s-p`r;R7-TMINKOpy zY8bZbC4}Oh^VD0LIN!16?{@02XFB0R)a=wpLpfwmKKn-GdJY>l&awX=vbG~RsQ`ZW7MuBADIq5XLEdE@RtS=F$2 z4Y+lCScC2kO~jv4c`h4K5z{5caLkL<=aYn~6`hh-p7MRPq0+K&-PYoR$Lp@C6O7iH zccPH%H8z{fkFC8+NA-6lrkJZQ8(5mLv5Ri|&40{R7F$^%1k@OApWi3#H`M>1wcEMd zQ4_7e+P|T`PTucd{tx`K@%@eb8~lo;nQ!19ZSdy`KkMszg$kD9zE$`~ZI))7T-zxe NthK^A(cIg&zW^A#wg~_L literal 0 HcmV?d00001 diff --git a/e2e/packageIO.spec.ts b/e2e/packageIO.spec.ts index e69de29..4293c5f 100644 --- a/e2e/packageIO.spec.ts +++ b/e2e/packageIO.spec.ts @@ -0,0 +1,69 @@ +import { test, expect } from '@playwright/test'; +import * as path from 'path'; + +test.describe('Package Upload and Download E2E Tests', () => { + const testPackageName = 'test-package'; + const testPackageVersion = '1.0.0'; + const testFileName = 'test-package.zip'; + const testFilePath = `e2e/assets/${testFileName}`; + const downloadDir = `e2e/downloads`; + +// test('upload a package', async ({ page }) => { +// await page.goto('http://localhost:4000'); + +// // Login +// await page.fill('input[name="username"]', 'ece30861defaultadminuser'); +// await page.fill( +// 'input[name="password"]', +// "correcthorsebatterystaple123(!__+@**(A'\"`;DROP TABLE packages;" +// ); +// await page.click('button:has-text("Login")'); +// await expect(page.getByText('Welcome, ece30861defaultadminuser!')).toBeVisible(); + +// // Navigate to the Upload Page +// await page.getByRole('link', { name: 'Upload' }).click(); + +// page.on('dialog', async (dialog) => { +// expect(dialog.message()).toBe('Package uploaded successfully!'); // Assert the alert message +// await dialog.accept(); // Accept the alert to close it +// }); +// // Fill out the upload form +// await page.getByLabel('Package Name:').fill(testPackageName); +// await page.getByLabel('Upload ZIP File:').setInputFiles(testFilePath); +// await page.getByRole('button', { name: 'Upload Package' }).click(); +// }); + + test('download a package', async ({ page, context }) => { + await page.goto('http://localhost:4000'); // Replace with your app URL + + // Login + await page.fill('input[name="username"]', 'ece30861defaultadminuser'); + await page.fill( + 'input[name="password"]', + "correcthorsebatterystaple123(!__+@**(A'\"`;DROP TABLE packages;" + ); + await page.click('button:has-text("Login")'); + await expect(page.getByText('Welcome, ece30861defaultadminuser!')).toBeVisible(); + + // Navigate to the Download Page + await page.getByRole('link', { name: 'Download' }).click(); + // Search for the package + await page.getByLabel('Search by Name or Regex:').fill(testPackageName); + await page.getByRole('button', { name: 'Search' }).click(); + await expect(page.getByText(`${testPackageName} (v${testPackageVersion})`)).toBeVisible(); + + // Start downloading the package + const [download] = await Promise.all([ + context.waitForEvent('download'), + page.click('button:has-text("Download")'), + ]); + + // Save the downloaded file + const downloadPath = path.join(downloadDir, testFileName); + await download.saveAs(downloadPath); + + // Verify the file exists + const fs = require('fs'); + expect(fs.existsSync(downloadPath)).toBeTruthy(); + }); +}); From 3aade5136d1909bfa544ff082fcf7b6b991a9fc2 Mon Sep 17 00:00:00 2001 From: te99y Date: Sun, 8 Dec 2024 22:01:17 -0500 Subject: [PATCH 9/9] allowed fetching group name in upload --- frontend/test/upload.test.tsx | 212 +++++++++++++++++++++++++++++----- 1 file changed, 180 insertions(+), 32 deletions(-) diff --git a/frontend/test/upload.test.tsx b/frontend/test/upload.test.tsx index dab7bb4..54169e0 100644 --- a/frontend/test/upload.test.tsx +++ b/frontend/test/upload.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import { MemoryRouter } from 'react-router-dom'; import UploadPage from '../src/pages/upload'; // Adjust the path if necessary @@ -7,34 +7,48 @@ import UploadPage from '../src/pages/upload'; // Adjust the path if necessary beforeEach(() => { jest.resetAllMocks(); localStorage.clear(); - global.fetch = jest.fn(); localStorage.setItem('authToken', 'mockAuthToken123'); + localStorage.setItem('userName', 'testuser'); + + // Mock fetch globally for this test suite + global.fetch = jest.fn(); + jest.spyOn(window, 'alert').mockImplementation(() => {}); // Mock alert }); -jest.spyOn(window, 'alert').mockImplementation(() => {}); // Mock alert -describe('UploadPage Component', () => { - test('renders the upload form', () => { - localStorage.setItem('authToken', 'mockAuthToken123'); - localStorage.setItem('userName', 'testuser'); +describe('UploadPage Component with Mocked useEffect and useState', () => { + + test('sets groupName and renders correctly without errors', async () => { + const mockGroupName = 'mockGroupName'; + + // Mock the fetch call for `/user/group` + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ groupName: mockGroupName }), + }); + render( ); - expect(screen.getByText('Upload a Package')).toBeInTheDocument(); - expect(screen.getByLabelText('Package Name:')).toBeInTheDocument(); - expect(screen.getByLabelText('Upload ZIP File:')).toBeInTheDocument(); - expect(screen.getByLabelText('Or Provide URL for the Package:')).toBeInTheDocument(); - expect(screen.getByText('Upload Package')).toBeInTheDocument(); + // Wait for the useEffect to resolve and verify groupName is displayed + await waitFor(() => { + const nameElement = screen.getAllByText((content, element) => + element?.textContent?.includes(`Mark as Secret (Accessible only by group: ${mockGroupName})`) || false + )[0]; + expect(nameElement).toBeInTheDocument(); + }); }); + test('handles uploading a ZIP file successfully', async () => { - localStorage.setItem('authToken', 'mockAuthToken123'); - localStorage.setItem('userName', 'testuser'); + const mockGroupName = 'mockGroupName'; + + // Mock the fetch call for `/user/group` (fetch as jest.Mock).mockResolvedValueOnce({ - status: 201, - json: async () => [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], + ok: true, + json: async () => ({ groupName: mockGroupName }), }); render( @@ -43,6 +57,11 @@ describe('UploadPage Component', () => { ); + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 201, + json: async () => [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], + }); + fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); const file = new File(['dummy content'], 'example.zip', { type: 'application/zip' }); @@ -59,12 +78,13 @@ describe('UploadPage Component', () => { }); }); - test('handles providing a URL for upload successfully', async () => { - localStorage.setItem('authToken', 'mockAuthToken123'); - localStorage.setItem('userName', 'testuser'); + test('handles providing a URL for upload successfully with isSecret checkbox checked', async () => { + const mockGroupName = 'mockGroupName'; + + // Mock the fetch call for `/user/group` (fetch as jest.Mock).mockResolvedValueOnce({ - status: 201, - json: async () => [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], + ok: true, + json: async () => ({ groupName: mockGroupName }), }); render( @@ -73,11 +93,19 @@ describe('UploadPage Component', () => { ); + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 201, + json: async () => [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], + }); + fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); fireEvent.change(screen.getByLabelText('Or Provide URL for the Package:'), { target: { value: 'https://example.com/package.zip' }, }); + const isSecretCheckbox = screen.getByLabelText(/Mark as Secret/); + fireEvent.click(isSecretCheckbox); // Mark as secret + fireEvent.click(screen.getByText('Upload Package')); await waitFor(() => { @@ -86,14 +114,19 @@ describe('UploadPage Component', () => { }); test('displays error when neither ZIP file nor URL is provided', async () => { - localStorage.setItem('authToken', 'mockAuthToken123'); - localStorage.setItem('userName', 'testuser'); + const mockGroupName = 'mockGroupName'; + + // Mock the fetch call for `/user/group` + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ groupName: mockGroupName }), + }); + render( ); - fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); fireEvent.click(screen.getByText('Upload Package')); @@ -103,9 +136,45 @@ describe('UploadPage Component', () => { }); }); + test('handles error when upload fails', async () => { + const mockGroupName = 'mockGroupName'; + + // Mock the fetch call for `/user/group` + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ groupName: mockGroupName }), + }); + render( + + + + ); + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 400, + }); + + + fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); + fireEvent.change(screen.getByLabelText('Or Provide URL for the Package:'), { + target: { value: 'https://example.com/package.zip' }, + }); + + fireEvent.click(screen.getByText('Upload Package')); + + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith('Upload failed: Missing fields or invalid data.'); + }); + }); + test('displays error for non-ZIP file uploads', async () => { - localStorage.setItem('authToken', 'mockAuthToken123'); - localStorage.setItem('userName', 'testuser'); + const mockGroupName = 'mockGroupName'; + + // Mock the fetch call for `/user/group` + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ groupName: mockGroupName }), + }); render( @@ -128,28 +197,107 @@ describe('UploadPage Component', () => { }); - test('handles error when upload fails', async () => { - localStorage.setItem('authToken', 'mockAuthToken123'); - localStorage.setItem('userName', 'testuser'); + test('handles providing a URL for upload successfully with isSecret checkbox checked', async () => { + const mockGroupName = 'mockGroupName'; + + // Mock the fetch call for `/user/group` (fetch as jest.Mock).mockResolvedValueOnce({ - status: 400, + ok: true, + json: async () => ({ groupName: mockGroupName }), + }); + render( + + + + ); + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 201, + json: async () => [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], + }); + + + fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); + fireEvent.change(screen.getByLabelText('Or Provide URL for the Package:'), { + target: { value: 'https://example.com/package.zip' }, }); + const isSecretCheckbox = screen.getByLabelText(/Mark as Secret/); + fireEvent.click(isSecretCheckbox); // Mark as secret + + fireEvent.click(screen.getByText('Upload Package')); + + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith('Package uploaded successfully!'); + }); + }); + + test('handles uploading a ZIP file successfully with isSecret checkbox unchecked', async () => { + const mockGroupName = 'mockGroupName'; + + // Mock the fetch call for `/user/group` + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ groupName: mockGroupName }), + }); render( ); + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 201, + json: async () => [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], + }); + + fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); + + const file = new File(['dummy content'], 'example.zip', { type: 'application/zip' }); + const fileInput = screen.getByLabelText('Upload ZIP File:') as HTMLInputElement; + fireEvent.change(fileInput, { target: { files: [file] } }); + + expect(fileInput.files![0]).toBe(file); + expect(fileInput.files!.length).toBe(1); + + fireEvent.click(screen.getByText('Upload Package')); + + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith('Package uploaded successfully!'); + }); + }); + + test('handles providing a URL for upload successfully with isSecret checkbox checked', async () => { + const mockGroupName = 'mockGroupName'; + + // Mock the fetch call for `/user/group` + (fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => ({ groupName: mockGroupName }), + }); + + render( + + + + ); + + (fetch as jest.Mock).mockResolvedValueOnce({ + status: 201, + json: async () => [{ ID: '1', Name: 'example-package', Version: '1.0.0' }], + }); fireEvent.change(screen.getByLabelText('Package Name:'), { target: { value: 'example-package' } }); fireEvent.change(screen.getByLabelText('Or Provide URL for the Package:'), { target: { value: 'https://example.com/package.zip' }, }); + const isSecretCheckbox = screen.getByLabelText(/Mark as Secret/); + fireEvent.click(isSecretCheckbox); // Mark as secret + fireEvent.click(screen.getByText('Upload Package')); await waitFor(() => { - expect(window.alert).toHaveBeenCalledWith('Upload failed: Missing fields or invalid data.'); + expect(window.alert).toHaveBeenCalledWith('Package uploaded successfully!'); }); }); -}); +}); \ No newline at end of file