Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
<title>UCSB Rec</title>
</head>
<body>
<div id="root"></div>
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/main/components/Nav/AppNavbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function AppNavbar({
>
<Container>
<Navbar.Brand as={Link} to="/">
Example
UCSB Rec
</Navbar.Brand>

<Navbar.Toggle />
Expand All @@ -49,6 +49,11 @@ export default function AppNavbar({

<Navbar.Collapse className="justify-content-between">
<Nav className="mr-auto">
{currentUser && currentUser.loggedIn && (
<Nav.Link as={Link} to="/requests/create">
Request Recommendation
</Nav.Link>
)}
{hasRole(currentUser, "ROLE_ADMIN") && (
<NavDropdown
title="Admin"
Expand Down
57 changes: 57 additions & 0 deletions frontend/src/tests/components/Nav/AppNavbar.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,61 @@ describe("AppNavbar tests", () => {
expect(screen.queryByText("Completed Requests")).not.toBeInTheDocument();
expect(screen.queryByText("Statistics")).not.toBeInTheDocument();
});

test("renders Request Recommendation link for logged in users", async () => {
const currentUser = currentUserFixtures.userOnly;
const doLogin = vi.fn();

render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<AppNavbar currentUser={currentUser} doLogin={doLogin} />
</MemoryRouter>
</QueryClientProvider>,
);

await screen.findByText("Request Recommendation");
const requestLink = screen.getByText("Request Recommendation");
expect(requestLink).toBeInTheDocument();
expect(requestLink).toHaveAttribute("href", "/requests/create");
});

test("Request Recommendation link appears for professor users", async () => {
const currentUser = currentUserFixtures.professorUser;
const doLogin = vi.fn();

render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<AppNavbar currentUser={currentUser} doLogin={doLogin} />
</MemoryRouter>
</QueryClientProvider>,
);

await screen.findByText("Request Recommendation");
const requestLink = screen.getByText("Request Recommendation");
expect(requestLink).toBeInTheDocument();
});

test("Request Recommendation link does not show when not logged in", async () => {
const currentUser = currentUserFixtures.notLoggedIn;
const systemInfo = systemInfoFixtures.showingBoth;
const doLogin = vi.fn();

render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<AppNavbar
currentUser={currentUser}
systemInfo={systemInfo}
doLogin={doLogin}
/>
</MemoryRouter>
</QueryClientProvider>,
);

expect(
screen.queryByText("Request Recommendation"),
).not.toBeInTheDocument();
});
});
11 changes: 11 additions & 0 deletions frontend/src/tests/indexHtml.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { readFileSync } from "fs";
import { resolve } from "path";
import { test, expect } from "vitest";

test("frontend/index.html has title 'UCSB Rec'", () => {
const indexPath = resolve(process.cwd(), "index.html");
const html = readFileSync(indexPath, "utf-8");
const m = html.match(/<title>(.*?)<\/title>/i);
expect(m).not.toBeNull();
expect(m[1]).toBe("UCSB Rec");
});
7 changes: 7 additions & 0 deletions src/main/java/edu/ucsb/cs156/rec/ExampleApplication.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.ucsb.cs156.rec;

import edu.ucsb.cs156.rec.services.RequestTypeService;
import edu.ucsb.cs156.rec.services.wiremock.WiremockService;
import java.time.ZonedDateTime;
import java.util.Optional;
Expand All @@ -21,6 +22,9 @@ public class ExampleApplication {

@Autowired WiremockService wiremockService;

@Autowired(required = false)
RequestTypeService requestTypeService;

@Bean
public DateTimeProvider utcDateTimeProvider() {
return () -> {
Expand Down Expand Up @@ -49,6 +53,9 @@ public ApplicationRunner wiremockApplicationRunner() {
public ApplicationRunner developmentApplicationRunner() {
return arg -> {
log.info("development mode");
if (requestTypeService != null) {
requestTypeService.loadRequestTypes();
}
log.info("developmentApplicationRunner completed");
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package edu.ucsb.cs156.rec.services;

import edu.ucsb.cs156.rec.entities.RequestType;
import edu.ucsb.cs156.rec.repositories.RequestTypeRepository;
import java.util.Arrays;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/** Service for managing Request Types. */
@Service
@Slf4j
public class RequestTypeService {

@Autowired private RequestTypeRepository requestTypeRepository;

/** List of hardcoded request types to be loaded at startup. */
private static final List<String> HARDCODED_REQUEST_TYPES =
Arrays.asList(
"CS Department BS/MS program",
"Scholarship or Fellowship",
"MS program (other than CS Dept BS/MS)",
"PhD program",
"Other");

/** Result class for loadRequestTypes method. */
@Data
@AllArgsConstructor
public static class LoadResult {
private int loaded;
private int skipped;
}

/**
* Load hardcoded request types into the database if they don't already exist.
*
* <p>This method checks for each hardcoded request type and only creates it if it's not already
* in the database.
*
* @return LoadResult containing the number of types loaded and skipped
*/
public LoadResult loadRequestTypes() {
log.info("Loading hardcoded request types...");
int loadedCount = 0;
int skippedCount = 0;

for (String type : HARDCODED_REQUEST_TYPES) {
if (requestTypeRepository.findByRequestType(type).isEmpty()) {
RequestType requestType = RequestType.builder().requestType(type).build();
requestTypeRepository.save(requestType);
log.info("Loaded request type: {}", type);
loadedCount++;
} else {
log.debug("Request type already exists, skipping: {}", type);
skippedCount++;
}
}

log.info("Request type loading completed. Loaded: {}, Skipped: {}", loadedCount, skippedCount);
return new LoadResult(loadedCount, skippedCount);
}
}
2 changes: 1 addition & 1 deletion src/main/resources/application-development.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
logging.level.sql=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
spring.datasource.url=jdbc:h2:file:./target/db-development
spring.datasource.url=jdbc:h2:file:./target/db-development;AUTO_SERVER=TRUE
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.settings.web-allow-others=true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package edu.ucsb.cs156.rec.services;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import edu.ucsb.cs156.rec.entities.RequestType;
import edu.ucsb.cs156.rec.repositories.RequestTypeRepository;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class RequestTypeServiceTest {

@Mock private RequestTypeRepository requestTypeRepository;

@InjectMocks private RequestTypeService requestTypeService;

@Test
public void test_loadRequestTypes_loadsAllTypesWhenNoneExist() {
// Arrange
when(requestTypeRepository.findByRequestType(any(String.class))).thenReturn(Optional.empty());

// Act
RequestTypeService.LoadResult result = requestTypeService.loadRequestTypes();

// Assert
// Should save 5 request types: CS Department BS/MS program, Scholarship or Fellowship,
// MS program (other than CS Dept BS/MS), PhD program, Other
verify(requestTypeRepository, times(5)).save(any(RequestType.class));
verify(requestTypeRepository, times(5)).findByRequestType(any(String.class));
assertEquals(5, result.getLoaded());
assertEquals(0, result.getSkipped());
}

@Test
public void test_loadRequestTypes_skipsExistingTypes() {
// Arrange
RequestType existingType = RequestType.builder().id(1L).requestType("Other").build();

// Mock that some types exist and some don't
when(requestTypeRepository.findByRequestType("CS Department BS/MS program"))
.thenReturn(Optional.empty());
when(requestTypeRepository.findByRequestType("Scholarship or Fellowship"))
.thenReturn(Optional.of(existingType));
when(requestTypeRepository.findByRequestType("MS program (other than CS Dept BS/MS)"))
.thenReturn(Optional.empty());
when(requestTypeRepository.findByRequestType("PhD program")).thenReturn(Optional.empty());
when(requestTypeRepository.findByRequestType("Other")).thenReturn(Optional.of(existingType));

// Act
RequestTypeService.LoadResult result = requestTypeService.loadRequestTypes();

// Assert
// Should only save 3 new types (skipping "Scholarship or Fellowship" and "Other")
verify(requestTypeRepository, times(3)).save(any(RequestType.class));
verify(requestTypeRepository, times(5)).findByRequestType(any(String.class));
assertEquals(3, result.getLoaded());
assertEquals(2, result.getSkipped());
}

@Test
public void test_loadRequestTypes_skipsAllWhenAllExist() {
// Arrange
RequestType existingType = RequestType.builder().id(1L).requestType("Some Type").build();

when(requestTypeRepository.findByRequestType(any(String.class)))
.thenReturn(Optional.of(existingType));

// Act
RequestTypeService.LoadResult result = requestTypeService.loadRequestTypes();

// Assert
// Should not save any types since all already exist
verify(requestTypeRepository, times(0)).save(any(RequestType.class));
verify(requestTypeRepository, times(5)).findByRequestType(any(String.class));
assertEquals(0, result.getLoaded());
assertEquals(5, result.getSkipped());
}
}
Loading