diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..5dbadb93
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,4 @@
+FROM openjdk:22
+ARG JAR_FILE=out/artifacts/coding_project_jar/*.jar
+COPY ${JAR_FILE} app.jar
+ENTRYPOINT ["java","-jar","/app.jar"]
\ No newline at end of file
diff --git a/README.md b/README.md
index 5b53a0b1..d32b96c2 100644
--- a/README.md
+++ b/README.md
@@ -1,38 +1,29 @@
-# Harbor Take Home Project
-Welcome to the Harbor take home project. We hope this is a good opportunity for you to showcase your skills.
+## MVP Features supported:
+- User is able to create, view, update, delete his profile
+- User is able to create, view , modify and delete his availability
+- User is able to share his entire availability to other users
+- User is able to see all overlaps with any set of registered users
+- user is able to create, view, update, cancel booking meeting
-## The Challenge
+## Test Coverage
+- Added unit test for userService.(Skipped other classes in the interest of time)
+- Exhaustive Integration Test Coverage using RestTemplate for all features except BookingController.
+- Integration test coverage ensures working Apis and its correctness
-Build us a REST API for calendly. Remember to support
+## Assumptions
+-Using InMemory Database for persisting data.
+-All users register on the application to be able to use the features
+- Have skipped some validation and incremental logic in some parts of application.Will keep on iterating on the same.
-- Setting own availability
-- Showing own availability
-- Finding overlap in schedule between 2 users
+## Future Improvements
+- Add logging framework
+- Emit appropriate metrics
+- Integrate Swagger
+- Integrate Java Cods Coverage
-It is up to you what else to support.
-## Expectations
-We care about
-- Have you thought through what a good MVP looks like? Does your API support that?
-- What trade-offs are you making in your design?
-- Working code - we should be able to pull and hit the code locally. Bonus points if deployed somewhere.
-- Any good engineer will make hacks when necessary - what are your hacks and why?
-We don't care about
-- Authentication
-- UI
-- Perfection - good and working quickly is better
-
-It is up to you how much time you want to spend on this project. There are likely diminishing returns as the time spent goes up.
-
-## Submission
-
-Please fork this repository and reach out to Prakash when finished.
-
-## Next Steps
-
-After submission, we will conduct a 30 to 60 minute code review in person. We will ask you about your thinking and design choices.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000..8c1eb7bf
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,85 @@
+
+
+4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.4
+
+
+org.p0
+calendly
+0.0.2-SNAPSHOT
+calendly
+Demo project for Spring Boot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+ provided
+
+
+
+
+ io.rest-assured
+ rest-assured
+ 5.5.0
+ test
+
+
+ junit
+ junit
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ 3.0.2
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.6.0
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/src/main/java/org/p0/calendly/CalendlyApplication.java b/src/main/java/org/p0/calendly/CalendlyApplication.java
new file mode 100644
index 00000000..da1be346
--- /dev/null
+++ b/src/main/java/org/p0/calendly/CalendlyApplication.java
@@ -0,0 +1,14 @@
+package org.p0.calendly;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CalendlyApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CalendlyApplication.class, args);
+ System.out.println("Hello");
+ }
+
+}
diff --git a/src/main/java/org/p0/calendly/controllers/BookingController.java b/src/main/java/org/p0/calendly/controllers/BookingController.java
new file mode 100644
index 00000000..f4a2a465
--- /dev/null
+++ b/src/main/java/org/p0/calendly/controllers/BookingController.java
@@ -0,0 +1,67 @@
+package org.p0.calendly.controllers;
+
+import lombok.NonNull;
+import org.p0.calendly.dtos.*;
+import org.p0.calendly.models.Booking;
+import org.p0.calendly.services.BookingManagementService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/v1/book")
+public class BookingController {
+
+
+ @Autowired
+ private BookingManagementService meetingManagementService;
+
+ // create booking
+ @PostMapping
+ public ResponseEntity createBooking(@RequestBody @NonNull MeetingBookingRequest meetingBookingRequest) {
+ // todo: input validations
+ String response = meetingManagementService.createBooking(meetingBookingRequest);
+
+ return ResponseEntity.status(201).body(response);
+ }
+
+ // Get booking by bookingID
+ @GetMapping("/{id}")
+ public ResponseEntity getBookingById(@PathVariable @NonNull String id) {
+ Booking response = null;
+ // todo: input validations
+ response= meetingManagementService.getBookingById(id);;
+
+ return ResponseEntity.status(200).body(response);
+ }
+
+ // Get booking by userId
+ @GetMapping("/user/{id}")
+ public ResponseEntity> getBookingByuserId(@PathVariable @NonNull String id) {
+ List response;
+
+ response= meetingManagementService.getBookingByuserId(id);
+
+ return ResponseEntity.status(200).body(response);
+ }
+
+ // Update booking by bookingId
+ @PutMapping("/{id}")
+ public ResponseEntity updateBooking(@PathVariable @NonNull String id, @RequestBody @NonNull Booking bookingDetails) {
+ Booking response = null;
+
+ response= meetingManagementService.updateBooking(id, bookingDetails);
+
+
+ return ResponseEntity.ok(response);
+ }
+
+ // Delete booking by bookingId
+ @DeleteMapping("/{id}")
+ public ResponseEntity deleteBooking(@PathVariable String id) {
+ meetingManagementService.deleteBooking(id);
+ return ResponseEntity.noContent().build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/p0/calendly/controllers/OverlapController.java b/src/main/java/org/p0/calendly/controllers/OverlapController.java
new file mode 100644
index 00000000..26d0ce31
--- /dev/null
+++ b/src/main/java/org/p0/calendly/controllers/OverlapController.java
@@ -0,0 +1,39 @@
+package org.p0.calendly.controllers;
+
+import lombok.NonNull;
+import org.p0.calendly.dtos.ScheduleOverlapRequest;
+import org.p0.calendly.dtos.ScheduleOverlapResponse;
+import org.p0.calendly.exceptions.NotEnoughUsersException;
+import org.p0.calendly.services.AvailabilityOverlapService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/v1/overlap")
+public class OverlapController {
+
+
+ @Autowired
+ private AvailabilityOverlapService availabilityOverlapService;
+
+ // get overlaps for comma separated userIds
+ @GetMapping("/ids={id}")
+ public ResponseEntity getOverlaps(@PathVariable @NonNull String id) {
+ // todo: validate input
+ List scheduleOverlapRequest = Arrays.asList(id.split(",", -1));
+
+ ScheduleOverlapResponse response = new ScheduleOverlapResponse();
+ try {
+ response = availabilityOverlapService.getUserOverlaps(scheduleOverlapRequest);
+ }
+ catch (NotEnoughUsersException e)
+ {
+ return ResponseEntity.status(400).build();
+ }
+ return ResponseEntity.status(200).body(response);
+ }
+}
diff --git a/src/main/java/org/p0/calendly/controllers/UserAvailabilityController.java b/src/main/java/org/p0/calendly/controllers/UserAvailabilityController.java
new file mode 100644
index 00000000..fa7e2158
--- /dev/null
+++ b/src/main/java/org/p0/calendly/controllers/UserAvailabilityController.java
@@ -0,0 +1,53 @@
+package org.p0.calendly.controllers;
+
+
+import lombok.NonNull;
+import org.p0.calendly.dtos.AvailabilityResponse;
+import org.p0.calendly.dtos.Availabilityrequest;
+import org.p0.calendly.exceptions.UserAlreadyExistsException;
+import org.p0.calendly.exceptions.UserNotFoundException;
+import org.p0.calendly.models.User;
+import org.p0.calendly.services.UserAvailabilityService;
+import org.p0.calendly.services.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/v1/availability")
+public class UserAvailabilityController {
+
+
+ @Autowired
+ private UserAvailabilityService userAvailabilityService;
+
+ // Create user availability
+ @PostMapping
+ public ResponseEntity createUserAvailability(@RequestBody @NonNull Availabilityrequest availabilityrequest) {
+
+ User response = null;
+ try {
+ userAvailabilityService.createUserAvailabilty(availabilityrequest);
+ }
+ catch (UserNotFoundException e)
+ {
+ return ResponseEntity.status(404).build();
+ }
+ return ResponseEntity.status(201).build();
+ }
+
+ // Get user availability
+ @GetMapping("/{id}")
+ public ResponseEntity getUserAvailability(@PathVariable @NonNull String id) {
+ AvailabilityResponse response = new AvailabilityResponse();
+ try {
+ response= userAvailabilityService.getUserAvailability(id);
+ }
+ catch (UserNotFoundException e)
+ {
+ return ResponseEntity.status(404).body(response);
+ }
+ return ResponseEntity.status(200).body(response);
+ }
+}
diff --git a/src/main/java/org/p0/calendly/controllers/UserProfileController.java b/src/main/java/org/p0/calendly/controllers/UserProfileController.java
new file mode 100644
index 00000000..a81b8ff4
--- /dev/null
+++ b/src/main/java/org/p0/calendly/controllers/UserProfileController.java
@@ -0,0 +1,75 @@
+package org.p0.calendly.controllers;
+
+
+import lombok.NonNull;
+import org.p0.calendly.exceptions.UserAlreadyExistsException;
+import org.p0.calendly.exceptions.UserNotFoundException;
+import org.p0.calendly.models.User;
+import org.p0.calendly.services.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/v1/user")
+public class UserProfileController {
+
+
+ @Autowired
+ private UserService userService;
+
+ // Create user
+ @PostMapping
+ public ResponseEntity createUser(@RequestBody @NonNull User user) {
+
+ User response = null;
+ try {
+ response= userService.createUser(user);
+ }
+ catch (UserAlreadyExistsException e)
+ {
+ //status 409 : conflict
+ return ResponseEntity.status(409).body(response);
+ }
+ return ResponseEntity.status(201).body(response);
+ }
+
+ // Get user by ID
+ @GetMapping("/{id}")
+ public ResponseEntity getUserById(@PathVariable @NonNull String id) {
+ User response = null;
+ try {
+ response= userService.getUserById(id);
+ }
+ catch (UserNotFoundException e)
+ {
+ //status 404 : not found
+ return ResponseEntity.status(404).build();
+ }
+ return ResponseEntity.status(200).body(response);
+
+ }
+
+ // Update user
+ @PutMapping("/{id}")
+ public ResponseEntity updateUser(@PathVariable @NonNull String id, @RequestBody @NonNull User userDetails) {
+ User response = null;
+
+ try {
+ response= userService.updateUser(id, userDetails);
+ }
+ catch (UserNotFoundException e)
+ {
+ //status 404 : conflict
+ return ResponseEntity.status(404).build();
+ }
+ return ResponseEntity.ok(response);
+ }
+
+ // Delete user
+ @DeleteMapping("/{id}")
+ public ResponseEntity deleteUser(@PathVariable String id) {
+ userService.deleteUser(id);
+ return ResponseEntity.noContent().build();
+ }
+}
diff --git a/src/main/java/org/p0/calendly/dtos/AvailabilityResponse.java b/src/main/java/org/p0/calendly/dtos/AvailabilityResponse.java
new file mode 100644
index 00000000..340b0e91
--- /dev/null
+++ b/src/main/java/org/p0/calendly/dtos/AvailabilityResponse.java
@@ -0,0 +1,15 @@
+package org.p0.calendly.dtos;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.p0.calendly.models.Schedule;
+
+import java.util.List;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+public class AvailabilityResponse {
+ private List scheduleList;
+}
diff --git a/src/main/java/org/p0/calendly/dtos/Availabilityrequest.java b/src/main/java/org/p0/calendly/dtos/Availabilityrequest.java
new file mode 100644
index 00000000..e6aacfee
--- /dev/null
+++ b/src/main/java/org/p0/calendly/dtos/Availabilityrequest.java
@@ -0,0 +1,14 @@
+package org.p0.calendly.dtos;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.p0.calendly.models.Schedule;
+
+import java.util.List;
+
+@AllArgsConstructor
+@Getter
+public class Availabilityrequest {
+ private String userId;
+ private List scheduleList;
+}
diff --git a/src/main/java/org/p0/calendly/dtos/MeetingBookingRequest.java b/src/main/java/org/p0/calendly/dtos/MeetingBookingRequest.java
new file mode 100644
index 00000000..935fe520
--- /dev/null
+++ b/src/main/java/org/p0/calendly/dtos/MeetingBookingRequest.java
@@ -0,0 +1,20 @@
+package org.p0.calendly.dtos;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.p0.calendly.models.enums.RecurrenceType;
+
+import java.util.List;
+
+@Getter
+@AllArgsConstructor
+public class MeetingBookingRequest {
+ private String organizer;
+ private long startDate;
+ private int startTime;
+ private int endTime;
+ private RecurrenceType recurrenceType;
+ private Long endDate;
+ private List audience;
+ //metadata
+}
\ No newline at end of file
diff --git a/src/main/java/org/p0/calendly/dtos/ScheduleOverlapRequest.java b/src/main/java/org/p0/calendly/dtos/ScheduleOverlapRequest.java
new file mode 100644
index 00000000..362fe4ea
--- /dev/null
+++ b/src/main/java/org/p0/calendly/dtos/ScheduleOverlapRequest.java
@@ -0,0 +1,12 @@
+package org.p0.calendly.dtos;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.List;
+
+@AllArgsConstructor
+@Getter
+public class ScheduleOverlapRequest {
+ private List users;
+}
diff --git a/src/main/java/org/p0/calendly/dtos/ScheduleOverlapResponse.java b/src/main/java/org/p0/calendly/dtos/ScheduleOverlapResponse.java
new file mode 100644
index 00000000..e1dbd68b
--- /dev/null
+++ b/src/main/java/org/p0/calendly/dtos/ScheduleOverlapResponse.java
@@ -0,0 +1,14 @@
+package org.p0.calendly.dtos;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.util.TreeMap;
+
+@NoArgsConstructor
+@Getter
+@AllArgsConstructor
+public class ScheduleOverlapResponse {
+ private TreeMap overlaps;
+}
diff --git a/src/main/java/org/p0/calendly/exceptions/NotEnoughUsersException.java b/src/main/java/org/p0/calendly/exceptions/NotEnoughUsersException.java
new file mode 100644
index 00000000..96fac59c
--- /dev/null
+++ b/src/main/java/org/p0/calendly/exceptions/NotEnoughUsersException.java
@@ -0,0 +1,4 @@
+package org.p0.calendly.exceptions;
+
+public class NotEnoughUsersException extends RuntimeException {
+}
diff --git a/src/main/java/org/p0/calendly/exceptions/UserAlreadyExistsException.java b/src/main/java/org/p0/calendly/exceptions/UserAlreadyExistsException.java
new file mode 100644
index 00000000..83a5ddf6
--- /dev/null
+++ b/src/main/java/org/p0/calendly/exceptions/UserAlreadyExistsException.java
@@ -0,0 +1,4 @@
+package org.p0.calendly.exceptions;
+
+public class UserAlreadyExistsException extends RuntimeException{
+}
diff --git a/src/main/java/org/p0/calendly/exceptions/UserNotFoundException.java b/src/main/java/org/p0/calendly/exceptions/UserNotFoundException.java
new file mode 100644
index 00000000..3125ec5e
--- /dev/null
+++ b/src/main/java/org/p0/calendly/exceptions/UserNotFoundException.java
@@ -0,0 +1,4 @@
+package org.p0.calendly.exceptions;
+
+public class UserNotFoundException extends RuntimeException{
+}
diff --git a/src/main/java/org/p0/calendly/models/Booking.java b/src/main/java/org/p0/calendly/models/Booking.java
new file mode 100644
index 00000000..94fe3103
--- /dev/null
+++ b/src/main/java/org/p0/calendly/models/Booking.java
@@ -0,0 +1,47 @@
+package org.p0.calendly.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.Setter;
+
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@Getter
+public class Booking {
+ private String bookingId;
+
+ private String linkedBookingId;
+ @Setter
+ private String organizer;
+ @Setter
+ private long startTime;
+ @Setter
+ private long endTime;
+ @Setter
+ private List audience;
+ //metadata
+
+ public Booking(@NonNull String organizer, long startTime, long endTime, @NonNull List audience,String linkedBookingId)
+ {
+ this.bookingId = this.setUniqueBookingId();
+ this.linkedBookingId = linkedBookingId ==null ? setUniquelinkedBookingId() : linkedBookingId;
+ this.audience = audience;
+ this.startTime = startTime;
+ this.endTime = endTime;
+ this.organizer = organizer;
+ }
+
+
+ private String setUniqueBookingId()
+ {
+ return UUID.randomUUID().toString();
+ }
+
+ private String setUniquelinkedBookingId()
+ {
+ return UUID.randomUUID().toString();
+ }
+}
diff --git a/src/main/java/org/p0/calendly/models/Schedule.java b/src/main/java/org/p0/calendly/models/Schedule.java
new file mode 100644
index 00000000..5687dbb8
--- /dev/null
+++ b/src/main/java/org/p0/calendly/models/Schedule.java
@@ -0,0 +1,19 @@
+package org.p0.calendly.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NonNull;
+import org.p0.calendly.models.enums.RecurrenceType;
+
+import java.util.List;
+
+@Getter
+@AllArgsConstructor
+public class Schedule {
+ private long startDate;
+ private long startTime;
+ private long endTime;
+ private RecurrenceType recurrenceType;
+ private Long endDate;
+ //metadata
+}
diff --git a/src/main/java/org/p0/calendly/models/User.java b/src/main/java/org/p0/calendly/models/User.java
new file mode 100644
index 00000000..3671d21e
--- /dev/null
+++ b/src/main/java/org/p0/calendly/models/User.java
@@ -0,0 +1,25 @@
+package org.p0.calendly.models;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.UUID;
+
+@Getter
+@AllArgsConstructor
+public class User {
+ private String id;
+ @Setter
+ private String firstName;
+ @Setter
+ private String lastName;
+ @Setter
+ private String email;
+
+ public void setUniqueId()
+ {
+ this.id = UUID.randomUUID().toString();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/p0/calendly/models/enums/RecurrenceType.java b/src/main/java/org/p0/calendly/models/enums/RecurrenceType.java
new file mode 100644
index 00000000..ffaee912
--- /dev/null
+++ b/src/main/java/org/p0/calendly/models/enums/RecurrenceType.java
@@ -0,0 +1,8 @@
+package org.p0.calendly.models.enums;
+
+public enum RecurrenceType {
+ NONE,
+ WEEKLY,
+ MONTHLY,
+ YEARLY
+}
diff --git a/src/main/java/org/p0/calendly/services/AvailabilityOverlapService.java b/src/main/java/org/p0/calendly/services/AvailabilityOverlapService.java
new file mode 100644
index 00000000..a35de0a0
--- /dev/null
+++ b/src/main/java/org/p0/calendly/services/AvailabilityOverlapService.java
@@ -0,0 +1,45 @@
+package org.p0.calendly.services;
+
+import lombok.NonNull;
+import org.p0.calendly.dtos.ScheduleOverlapRequest;
+import org.p0.calendly.dtos.ScheduleOverlapResponse;
+import org.p0.calendly.exceptions.NotEnoughUsersException;
+import org.p0.calendly.strategies.OverlapStrategy;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.TreeMap;
+
+@Service
+public class AvailabilityOverlapService {
+ @Autowired
+ private UserAvailabilityService userAvailabilityService;
+
+ @Autowired
+ private OverlapStrategy overlapStrategy;
+
+ public ScheduleOverlapResponse getUserOverlaps(@NonNull List scheduleOverlapRequest) {
+
+ if(scheduleOverlapRequest == null || scheduleOverlapRequest.size() < 2)
+ throw new NotEnoughUsersException();
+
+ return getOverlaps(scheduleOverlapRequest);
+ }
+
+ private ScheduleOverlapResponse getOverlaps(List users)
+ {
+ // Todo: check user validation
+ String userId = users.get(0);
+
+ TreeMap userAvailability = userAvailabilityService.getAvailability(userId);
+
+ for (int i=1;i neighbourAvailability = userAvailabilityService.getAvailability(users.get(i));
+ userAvailability = overlapStrategy.findOverlaps(userAvailability, neighbourAvailability);
+ }
+
+ return new ScheduleOverlapResponse(userAvailability);
+ }
+}
diff --git a/src/main/java/org/p0/calendly/services/BookingManagementService.java b/src/main/java/org/p0/calendly/services/BookingManagementService.java
new file mode 100644
index 00000000..67c325bc
--- /dev/null
+++ b/src/main/java/org/p0/calendly/services/BookingManagementService.java
@@ -0,0 +1,178 @@
+package org.p0.calendly.services;
+
+import lombok.NonNull;
+import org.p0.calendly.dtos.*;
+import org.p0.calendly.models.Booking;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+@Service
+public class BookingManagementService {
+ private Map bookingMap = new HashMap<>();
+ private Map> userBookingMap = new HashMap<>();
+ private Map> recurringBookingMap = new HashMap<>();
+
+ @Autowired
+ private UserAvailabilityService userAvailabilityService;
+
+ public String createBooking(@NonNull MeetingBookingRequest meetingBookingRequest) {
+ return book(meetingBookingRequest);
+ }
+
+ public Booking getBookingById(@NonNull String id) {
+ return bookingMap.get(id);
+ }
+
+ public List getBookingByuserId(@NonNull String id) {
+ List bookingIds = userBookingMap.get(id);
+ List bookingList = new ArrayList<>();
+ for(String bookingId : bookingIds)
+ {
+ bookingList.add(bookingMap.get(bookingId));
+ }
+ return bookingList;
+ }
+
+ public Booking updateBooking(@NonNull String id, @NonNull Booking bookingDetails) {
+ if(!bookingMap.containsKey(id))
+ {
+ // todo : BookingNOTFOUNDEXCEPTION
+ }
+
+
+ Booking booking = bookingMap.get(id);
+ booking.setOrganizer(bookingDetails.getOrganizer());
+ booking.setStartTime(bookingDetails.getStartTime());
+ booking.setEndTime(bookingDetails.getEndTime());
+ booking.setAudience(bookingDetails.getAudience());
+ bookingMap.put(id,booking);
+ // Todo : Update availability
+ return booking;
+ }
+
+ public void deleteBooking(String id) {
+ bookingMap.remove(id);
+ // Todo : Update availability
+ }
+
+
+
+
+ private String book(MeetingBookingRequest request)
+ {
+ Long startDate = request.getStartDate();
+ Long startTime = Long.valueOf(request.getStartTime());
+ Long endTime = Long.valueOf(request.getEndTime());
+ Long endDate = request.getEndDate();
+
+ switch (request.getRecurrenceType())
+ {
+ case NONE ->{
+ if(userAvailabilityService.isSlotAvailable(request.getOrganizer(), startDate+startTime,
+ startDate + endTime))
+ {
+ userAvailabilityService.updateAvailability(request.getOrganizer(), startDate+startTime,
+ startDate + endTime);
+
+ Booking booking = new Booking(request.getOrganizer(),
+ startDate+startTime,
+ startDate+endTime,request.getAudience(), null);
+ bookingMap.put(booking.getBookingId(), booking);
+
+ //update recurringmap
+ if(recurringBookingMap.containsKey(booking.getLinkedBookingId()))
+ {
+ List bookingIds = recurringBookingMap.get(booking.getLinkedBookingId());
+ bookingIds.add(booking.getBookingId());
+ recurringBookingMap.put(booking.getLinkedBookingId(), bookingIds);
+ }
+ else
+ {
+ List bookingIds = new ArrayList<>();
+ bookingIds.add(booking.getBookingId());
+ recurringBookingMap.put(booking.getLinkedBookingId(), bookingIds);
+ }
+
+ //update usermap
+ if(userBookingMap.containsKey(booking.getOrganizer()))
+ {
+ List bookingIds = userBookingMap.get(booking.getOrganizer());
+ bookingIds.add(booking.getBookingId());
+ userBookingMap.put(booking.getLinkedBookingId(), bookingIds);
+ }
+ else
+ {
+ List bookingIds = new ArrayList<>();
+ bookingIds.add(booking.getBookingId());
+ userBookingMap.put(booking.getOrganizer(), bookingIds);
+ }
+
+ return booking.getBookingId();
+ }
+
+ }
+
+ case WEEKLY -> {
+ Long weeklyEpoch =Long.valueOf(7* 86400000);
+ Long tempStartDate = startDate;
+ String linkedBookingId = null;
+ while(endDate <= startDate)
+ {
+
+ if(userAvailabilityService.isSlotAvailable(request.getOrganizer(), startDate+startTime,
+ startDate + endTime))
+ {
+ userAvailabilityService.updateAvailability(request.getOrganizer(), startDate+startTime,
+ startDate + endTime);
+ Booking booking = new Booking(request.getOrganizer(),
+ startDate+startTime,startDate+endTime,request.getAudience(),linkedBookingId);
+ bookingMap.put(booking.getBookingId(), booking);
+
+ //update recurringmap
+ if(recurringBookingMap.containsKey(booking.getLinkedBookingId()))
+ {
+ List bookingIds = recurringBookingMap.get(booking.getLinkedBookingId());
+ bookingIds.add(booking.getBookingId());
+ recurringBookingMap.put(booking.getLinkedBookingId(), bookingIds);
+ }
+ else
+ {
+ List bookingIds = new ArrayList<>();
+ bookingIds.add(booking.getBookingId());
+ recurringBookingMap.put(booking.getLinkedBookingId(), bookingIds);
+ }
+
+ //update usermap
+ if(userBookingMap.containsKey(booking.getOrganizer()))
+ {
+ List bookingIds = userBookingMap.get(booking.getOrganizer());
+ bookingIds.add(booking.getBookingId());
+ userBookingMap.put(booking.getLinkedBookingId(), bookingIds);
+ }
+ else
+ {
+ List bookingIds = new ArrayList<>();
+ bookingIds.add(booking.getBookingId());
+ userBookingMap.put(booking.getOrganizer(), bookingIds);
+ }
+ linkedBookingId = booking.getLinkedBookingId();
+ }
+ tempStartDate +=weeklyEpoch;
+ }
+ return linkedBookingId;
+ }
+ case MONTHLY -> {
+ //TOdo: add monthly
+ }
+ case YEARLY -> {
+ //TOdo: add yearly
+ }
+ }
+
+//Handle using NO_SLOT_AVAILABLE_EXCEPTION
+ return null;
+ }
+}
+
diff --git a/src/main/java/org/p0/calendly/services/UserAvailabilityService.java b/src/main/java/org/p0/calendly/services/UserAvailabilityService.java
new file mode 100644
index 00000000..b1f4700c
--- /dev/null
+++ b/src/main/java/org/p0/calendly/services/UserAvailabilityService.java
@@ -0,0 +1,143 @@
+package org.p0.calendly.services;
+
+import org.p0.calendly.dtos.AvailabilityResponse;
+import org.p0.calendly.dtos.Availabilityrequest;
+import org.p0.calendly.exceptions.UserNotFoundException;
+import org.p0.calendly.models.Schedule;
+import org.p0.calendly.models.User;
+import org.p0.calendly.models.enums.RecurrenceType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+@Service
+public class UserAvailabilityService {
+
+ private Map> userAvailabilityMap = new HashMap<>();
+
+ @Autowired
+ private UserService userService;
+
+ // Create a new user
+ public void createUserAvailabilty(final Availabilityrequest availabilityrequest) {
+
+ User user = userService.getUserById(availabilityrequest.getUserId());
+
+ userAvailabilityMap.put(availabilityrequest.getUserId(),addUserSchedule(availabilityrequest.getScheduleList()));
+
+ }
+ public AvailabilityResponse getUserAvailability(String id) {
+ User user = userService.getUserById(id);
+
+ return new AvailabilityResponse(convertTimestampToSchedule(userAvailabilityMap.get(id)));
+ }
+
+ public TreeMap getAvailability(String id)
+ {
+ return userAvailabilityMap.get(id);
+ }
+
+ public boolean isSlotAvailable(String userId, Long start, Long end)
+ {
+ if(!userAvailabilityMap.containsKey(userId))
+ throw new UserNotFoundException();
+
+ TreeMap availability = userAvailabilityMap.get(userId);
+
+ if(availability.containsKey(start) && availability.get(start) >= end)
+ return true;
+ if(availability.lowerKey(start) != null && availability.get(availability.lowerKey(start)) >= end)
+ return true;
+
+ return false;
+ }
+
+ public void updateAvailability(String userId, Long start, Long end)
+ {
+ TreeMap availability = userAvailabilityMap.get(userId);
+
+ if(availability.containsKey(start) && availability.get(start) == end)
+ {
+ availability.remove(start);
+
+ }
+ else if(availability.containsKey(start) && availability.get(start) > end)
+ {
+ availability.put(end,availability.get(start));
+ availability.remove(start);
+
+ }
+ else if(availability.lowerKey(start) != null && availability.get(availability.lowerKey(start)) == end)
+ {
+ availability.put(availability.lowerKey(start),start);
+
+ }
+ else if(availability.lowerKey(start) != null && availability.get(availability.lowerKey(start)) >= end)
+ {
+ Long value = availability.get(availability.lowerKey(start));
+ availability.put(availability.lowerKey(start),start);
+ availability.put(end,value);
+
+ }
+ }
+
+ private List convertTimestampToSchedule(TreeMap map)
+ {
+ Long weeklyEpoch =Long.valueOf(7* 86400000);
+ List scheduleList = new ArrayList<>();
+ map.forEach((k,v) -> {
+ Long tempKey = k;
+ Long tempValue = v;
+ while(map.containsKey(tempKey + weeklyEpoch))
+ {
+ tempValue = map.get(tempKey + weeklyEpoch);
+ map.remove(tempKey);
+ tempKey = tempKey + weeklyEpoch;
+
+ }
+
+ RecurrenceType type = tempKey != k ? RecurrenceType.WEEKLY : RecurrenceType.NONE;
+ scheduleList.add(new Schedule(tempKey - (tempKey% 86400000),
+ (tempKey % 86400000), (tempValue% 86400000),
+ type, tempValue - (tempValue % 86400000) + 86400000));
+ });
+
+ return scheduleList;
+ }
+
+ private TreeMap addUserSchedule(List scheduleList)
+ {
+ TreeMap scheduleMap = new TreeMap<>();
+
+ for(Schedule schedule : scheduleList)
+ {
+ Long startDate = schedule.getStartDate();
+ Long startTime = Long.valueOf(schedule.getStartTime());
+ Long endTime = Long.valueOf(schedule.getEndTime());
+ Long endDate = schedule.getEndDate();
+
+ switch (schedule.getRecurrenceType())
+ {
+ case NONE -> scheduleMap.put(startDate+startTime, startDate+endTime);
+ case WEEKLY -> {
+ Long weeklyEpoch =Long.valueOf(7* 86400000);
+ Long tempStartDate = startDate;
+ while(endDate <= startDate)
+ {
+ scheduleMap.put(startDate+startTime, startDate+endTime);
+ tempStartDate +=weeklyEpoch;
+ }
+ }
+ case MONTHLY -> {
+ //TOdo: add monthly
+ }
+ case YEARLY -> {
+ //TOdo: add yearly
+ }
+ }
+ }
+
+ return scheduleMap;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/p0/calendly/services/UserService.java b/src/main/java/org/p0/calendly/services/UserService.java
new file mode 100644
index 00000000..8aa6516e
--- /dev/null
+++ b/src/main/java/org/p0/calendly/services/UserService.java
@@ -0,0 +1,59 @@
+package org.p0.calendly.services;
+
+import org.p0.calendly.exceptions.UserAlreadyExistsException;
+import org.p0.calendly.exceptions.UserNotFoundException;
+import org.p0.calendly.models.User;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class UserService {
+ private Map userMap = new HashMap<>();
+
+ // Create a new user
+ public User createUser(User user) {
+ if(user.getId() == null || user.getId().isEmpty())
+ user.setUniqueId();
+
+ if(userMap.containsKey(user.getId()))
+ {
+ throw new UserAlreadyExistsException();
+ }
+
+ userMap.put(user.getId(),user);
+
+ return user;
+ }
+
+ // Retrieve a single user by ID
+ public User getUserById(String id) {
+ if(userMap.containsKey(id))
+ return userMap.get(id);
+
+ throw new UserNotFoundException();
+
+ }
+
+ // Update user details
+ public User updateUser(String id, User userDetails) {
+ if(!userMap.containsKey(id))
+ {
+ throw new UserNotFoundException();
+ }
+
+
+ User user = userMap.get(id);
+ user.setFirstName(userDetails.getFirstName());
+ user.setLastName(userDetails.getLastName());
+ user.setEmail(userDetails.getEmail());
+ userMap.put(user.getId(),user);
+ return user;
+ }
+
+ // Delete user by ID
+ public void deleteUser(String id) {
+ userMap.remove(id);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/p0/calendly/strategies/DefaultOverlapStrategy.java b/src/main/java/org/p0/calendly/strategies/DefaultOverlapStrategy.java
new file mode 100644
index 00000000..7b2d714d
--- /dev/null
+++ b/src/main/java/org/p0/calendly/strategies/DefaultOverlapStrategy.java
@@ -0,0 +1,54 @@
+package org.p0.calendly.strategies;
+
+import org.springframework.stereotype.Service;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeMap;
+
+@Service
+public class DefaultOverlapStrategy implements OverlapStrategy{
+
+ @Override
+ public TreeMap findOverlaps(TreeMap map1, TreeMap map2) {
+ TreeMap overlapMap = new TreeMap<>();
+
+ if(map1 == null || map1.isEmpty() || map2 == null || map2.isEmpty())
+ return overlapMap;
+
+ // Iterators for both maps
+ Iterator> iterator1 = map1.entrySet().iterator();
+ Iterator> iterator2 = map2.entrySet().iterator();
+
+ // Get first intervals from both maps
+ Map.Entry interval1 = iterator1.hasNext() ? iterator1.next() : null;
+ Map.Entry interval2 = iterator2.hasNext() ? iterator2.next() : null;
+
+ // Traverse both maps
+ while (interval1 != null && interval2 != null) {
+ long start1 = interval1.getKey();
+ long end1 = interval1.getValue();
+ long start2 = interval2.getKey();
+ long end2 = interval2.getValue();
+
+ // Find the maximum of the starting points and the minimum of the ending points
+ long overlapStart = Math.max(start1, start2);
+ long overlapEnd = Math.min(end1, end2);
+
+ // Check if there is an overlap
+ if (overlapStart <= overlapEnd) {
+ overlapMap.put(overlapStart, overlapEnd);
+ }
+
+ // Move the iterator with the smaller ending point forward
+ if (end1 < end2) {
+ interval1 = iterator1.hasNext() ? iterator1.next() : null;
+ } else {
+ interval2 = iterator2.hasNext() ? iterator2.next() : null;
+ }
+ }
+
+ return overlapMap;
+ }
+}
diff --git a/src/main/java/org/p0/calendly/strategies/OverlapStrategy.java b/src/main/java/org/p0/calendly/strategies/OverlapStrategy.java
new file mode 100644
index 00000000..11ce5500
--- /dev/null
+++ b/src/main/java/org/p0/calendly/strategies/OverlapStrategy.java
@@ -0,0 +1,8 @@
+package org.p0.calendly.strategies;
+
+import java.util.TreeMap;
+
+public interface OverlapStrategy {
+
+ public TreeMap findOverlaps(TreeMap u1, TreeMap u2);
+}
diff --git a/src/main/java/org/p0/calendly/utils/RestTemplateClient.java b/src/main/java/org/p0/calendly/utils/RestTemplateClient.java
new file mode 100644
index 00000000..648edd1a
--- /dev/null
+++ b/src/main/java/org/p0/calendly/utils/RestTemplateClient.java
@@ -0,0 +1,10 @@
+package org.p0.calendly.utils;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class RestTemplateClient extends RestTemplate {
+
+}
diff --git a/src/test/java/ApiIntegrationTests/OverlapControllerIntegrationTests.java b/src/test/java/ApiIntegrationTests/OverlapControllerIntegrationTests.java
new file mode 100644
index 00000000..68f2cf66
--- /dev/null
+++ b/src/test/java/ApiIntegrationTests/OverlapControllerIntegrationTests.java
@@ -0,0 +1,138 @@
+package ApiIntegrationTests;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.p0.calendly.CalendlyApplication;
+import org.p0.calendly.dtos.Availabilityrequest;
+import org.p0.calendly.dtos.ScheduleOverlapResponse;
+import org.p0.calendly.models.Schedule;
+import org.p0.calendly.models.User;
+import org.p0.calendly.models.enums.RecurrenceType;
+import org.p0.calendly.utils.RestTemplateClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.http.*;
+import org.springframework.web.client.HttpClientErrorException;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+
+@SpringBootTest(
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ classes = CalendlyApplication.class)
+
+public class OverlapControllerIntegrationTests {
+
+ @LocalServerPort
+ private int port;
+
+
+ private String userBaseUrl, availabilityBaseUrl, overlapBaseUrl;
+ private String user1Id, user2Id, user3Id;
+
+ @Autowired
+ private RestTemplateClient restTemplate;
+
+ @BeforeEach
+ public void setUp() {
+ availabilityBaseUrl = "http://localhost:" + port + "/api/v1/availability";
+ userBaseUrl = "http://localhost:" + port + "/api/v1/user";
+ overlapBaseUrl = "http://localhost:" + port + "/api/v1/overlap";
+
+ // refractor setup method to use TESTUTILS for creating user and schedule
+
+ //create user1
+ User user1 = new User(null, "Elizabeth", "Swann", "elizabeth.swann@example.com");
+ ResponseEntity response1 = restTemplate.postForEntity(userBaseUrl, user1, User.class);
+ user1Id = response1.getBody().getId().toString();
+
+ // First create availability for a user
+ // start date 1704844800000L = 2024/01/10 00:00:00 UTC
+ //end date 1704931200000L = 2024/01/11 00:00:00 UTC
+ Schedule schedule1 = new Schedule(1704844800000L, 4800000, 8400000, RecurrenceType.NONE, 1704931200000L);
+ List scheduleList1 = Arrays.asList(schedule1);
+ //Availabilityrequest availabilityRequest = new Availabilityrequest(user1Id, scheduleList);
+ restTemplate.postForEntity(availabilityBaseUrl, new Availabilityrequest(user1Id, scheduleList1), Void.class);
+
+
+
+ //create user2
+ User user2 = new User(null, "Amit", "G", "a.g@example.com");
+ ResponseEntity response2 = restTemplate.postForEntity(userBaseUrl, user2, User.class);
+ user2Id = response2.getBody().getId().toString();
+
+ // First create availability for a user
+ // start date 1704844800000L = 2024/01/10 00:00:00 UTC
+ //end date 1704931200000L = 2024/01/11 00:00:00 UTC
+ Schedule schedule2 = new Schedule(1704844800000L, 3600000, 7200000, RecurrenceType.NONE, 1704931200000L);
+ List scheduleList2 = Arrays.asList(schedule2);
+ //Availabilityrequest availabilityRequest = new Availabilityrequest(id, scheduleList);
+ restTemplate.postForEntity(availabilityBaseUrl, new Availabilityrequest(user2Id, scheduleList2), Void.class);
+
+
+
+ //create user3
+ User user3 = new User(null, "Ekan", "S", "e.s@example.com");
+ ResponseEntity response3 = restTemplate.postForEntity(userBaseUrl, user3, User.class);
+ user3Id = response3.getBody().getId().toString();
+
+ // First create availability for a user
+ // start date 1705708800000L = 2024/01/20 00:00:00 UTC
+ //end date 1705795200000L = 2024/01/21 00:00:00 UTC
+ Schedule schedule3 = new Schedule(1705708800000L, 3600000, 7200000, RecurrenceType.NONE, 1705795200000L);
+ List scheduleList3 = Arrays.asList(schedule3);
+ //Availabilityrequest availabilityRequest = new Availabilityrequest(id, scheduleList);
+ restTemplate.postForEntity(availabilityBaseUrl, new Availabilityrequest(user3Id, scheduleList3), Void.class);
+
+ }
+
+ // // Test Get User Availability with meaningful method name
+ @Test
+ public void getUserAvailability_ShouldReturn200_WhenUserExists() {
+ // Setup the ScheduleOverlapRequest with user IDs
+ String id = user1Id + "," + user2Id;
+
+ // Now retrieve the availability for the user
+ ResponseEntity responseEntity = restTemplate.getForEntity(overlapBaseUrl + "/ids={id}", ScheduleOverlapResponse.class, id);
+
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ assertNotNull(responseEntity.getBody());
+ assertEquals(1, responseEntity.getBody().getOverlaps().size());
+ assertEquals(1704849600000L, responseEntity.getBody().getOverlaps().firstEntry().getKey());
+ assertEquals(1704852000000L, responseEntity.getBody().getOverlaps().firstEntry().getValue());
+ }
+
+ // Test for NotEnoughUsersException
+ @Test
+ public void getOverlaps_ShouldReturn400_WhenNotEnoughUsersProvided() {
+ // Setup the ScheduleOverlapRequest with a single user (which should trigger NotEnoughUsersException)
+ String id = user1Id ;
+
+ try {
+ // Now retrieve the availability for the user
+ ResponseEntity responseEntity = restTemplate.getForEntity(overlapBaseUrl + "/ids={id}", ScheduleOverlapResponse.class, id);
+ } catch (HttpClientErrorException ex) {
+ // Validate that a 400 Bad Request is returned
+ assertEquals(HttpStatus.BAD_REQUEST, ex.getStatusCode());
+ }
+ }
+
+ @Test
+ public void getUserAvailability_ShouldReturnEmpty_WhenNoOverlaps() {
+ // Setup the ScheduleOverlapRequest with user IDs
+ String id = user1Id + "," + user3Id;
+
+ // Now retrieve the availability for the user
+ ResponseEntity responseEntity = restTemplate.getForEntity(overlapBaseUrl + "/ids={id}", ScheduleOverlapResponse.class, id);
+
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ assertNotNull(responseEntity.getBody());
+ assertEquals(0, responseEntity.getBody().getOverlaps().size());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ApiIntegrationTests/UserAvailabilityControllerIntegrationTests.java b/src/test/java/ApiIntegrationTests/UserAvailabilityControllerIntegrationTests.java
new file mode 100644
index 00000000..6334a250
--- /dev/null
+++ b/src/test/java/ApiIntegrationTests/UserAvailabilityControllerIntegrationTests.java
@@ -0,0 +1,115 @@
+package ApiIntegrationTests;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.p0.calendly.CalendlyApplication;
+import org.p0.calendly.dtos.AvailabilityResponse;
+import org.p0.calendly.dtos.Availabilityrequest;
+import org.p0.calendly.models.Schedule;
+import org.p0.calendly.models.User;
+import org.p0.calendly.models.enums.RecurrenceType;
+import org.p0.calendly.utils.RestTemplateClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.http.*;
+ import org.springframework.web.client.HttpClientErrorException;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+
+@SpringBootTest(
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ classes = CalendlyApplication.class)
+
+public class UserAvailabilityControllerIntegrationTests {
+
+ @LocalServerPort
+ private int port;
+
+
+ private String userBaseUrl;
+ private String availabilityBaseUrl;
+
+ @Autowired
+ private RestTemplateClient restTemplate;
+
+ @BeforeEach
+ public void setUp() {
+ availabilityBaseUrl = "http://localhost:" + port + "/api/v1/availability";
+ userBaseUrl = "http://localhost:" + port + "/api/v1/user";
+ }
+
+ // Test Create User Availability with meaningful method name
+ @Test
+ public void createUserAvailability_ShouldReturn201_WhenAvailabilityIsCreatedSuccessfully() {
+ // create user first
+ User newUser = new User(null, "Elizabeth", "Swann", "elizabeth.swann@example.com");
+ ResponseEntity response = restTemplate.postForEntity(userBaseUrl, newUser, User.class);
+ String id = response.getBody().getId().toString();
+
+ Schedule schedule = new Schedule(1696176000000L, 900, 1200, RecurrenceType.WEEKLY, 1696262400000L);
+ List scheduleList = Arrays.asList(schedule);
+ Availabilityrequest availabilityRequest = new Availabilityrequest(id, scheduleList);
+
+ ResponseEntity responseEntity = restTemplate.postForEntity(availabilityBaseUrl, availabilityRequest, Void.class);
+
+ assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
+ }
+
+ // Test Create User Availability with User Not Found
+ @Test
+ public void createUserAvailability_ShouldReturn404_WhenUserNotFound() {
+ Schedule schedule = new Schedule(1696176000000L, 900, 1200, RecurrenceType.WEEKLY, 1696262400000L);
+ List scheduleList = Arrays.asList(schedule);
+ Availabilityrequest availabilityRequest = new Availabilityrequest("nonexistent_id", scheduleList);
+
+ try {
+ restTemplate.postForEntity(availabilityBaseUrl, availabilityRequest, Void.class);
+
+ } catch (HttpClientErrorException ex) {
+ assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode());
+ }
+ }
+//
+// // Test Get User Availability with meaningful method name
+ @Test
+ public void getUserAvailability_ShouldReturn200_WhenUserExists() {
+ //create user
+ User newUser = new User(null, "Elizabeth", "Swann", "elizabeth.swann@example.com");
+ ResponseEntity response = restTemplate.postForEntity(userBaseUrl, newUser, User.class);
+ String id = response.getBody().getId().toString();
+
+ // First create availability for a user
+ // start date 1704844800000L = 2024/01/10 00:00:00 UTC
+ //end date 1704931200000L = 2024/01/11 00:00:00 UTC
+ Schedule schedule = new Schedule(1704844800000L, 3600000, 7200000, RecurrenceType.NONE, 1704931200000L);
+ List scheduleList = Arrays.asList(schedule);
+ Availabilityrequest availabilityRequest = new Availabilityrequest(id, scheduleList);
+ restTemplate.postForEntity(availabilityBaseUrl, availabilityRequest, Void.class);
+
+ // Now retrieve the availability for the user
+ ResponseEntity responseEntity = restTemplate.getForEntity(availabilityBaseUrl + "/{id}", AvailabilityResponse.class, id);
+
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ assertNotNull(responseEntity.getBody());
+ assertEquals(1, responseEntity.getBody().getScheduleList().size());
+ assertEquals(3600000, responseEntity.getBody().getScheduleList().get(0).getStartTime());
+ assertEquals(RecurrenceType.NONE, responseEntity.getBody().getScheduleList().get(0).getRecurrenceType());
+ }
+//
+// // Test Get User Availability with User Not Found
+ @Test
+ public void getUserAvailability_ShouldReturn404_WhenUserNotFound() {
+ try {
+ restTemplate.getForEntity(availabilityBaseUrl + "/{id}", AvailabilityResponse.class, "nonexistent_id");
+ } catch (HttpClientErrorException ex) {
+ assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode());
+ }
+ }
+}
diff --git a/src/test/java/ApiIntegrationTests/UserProfileControllerIntegrationTests.java b/src/test/java/ApiIntegrationTests/UserProfileControllerIntegrationTests.java
new file mode 100644
index 00000000..8f5d7c84
--- /dev/null
+++ b/src/test/java/ApiIntegrationTests/UserProfileControllerIntegrationTests.java
@@ -0,0 +1,122 @@
+package ApiIntegrationTests;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.p0.calendly.CalendlyApplication;
+import org.p0.calendly.models.User;
+import org.p0.calendly.utils.RestTemplateClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.http.*;
+import org.springframework.web.client.HttpClientErrorException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+
+@SpringBootTest(
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ classes = CalendlyApplication.class)
+
+public class UserProfileControllerIntegrationTests {
+
+ @LocalServerPort
+ private int port;
+
+
+ private String baseUrl;
+
+ @Autowired
+ private RestTemplateClient restTemplate;
+
+ @BeforeEach
+ public void setUp() {
+ baseUrl = "http://localhost:" + port + "/api/v1/user";
+ }
+
+ // Test Create User
+ @Test
+ public void createUser_ShouldReturn201_WhenUserIsCreatedSuccessfully() {
+ User newUser = new User(null, "John", "Doe", "john.doe@example.com");
+
+ ResponseEntity responseEntity = restTemplate.postForEntity(baseUrl, newUser, User.class);
+
+ assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
+ assertNotNull(responseEntity.getBody());
+ assertNotNull(responseEntity.getBody().getId());
+ assertEquals("John", responseEntity.getBody().getFirstName());
+
+ }
+
+ @Test
+ public void createUser_ShouldReturn409_WhenUserAlreadyExists() {
+ // First create a user
+ User newUser = new User(null, "Elizabeth", "Swann", "elizabeth.swann@example.com");
+ ResponseEntity response = restTemplate.postForEntity(baseUrl, newUser, User.class);
+ String id = response.getBody().getId().toString();
+
+ // Try to create the same user again
+ User sameUser = new User(id, "Elizabeth", "Swann", "elizabeth.swann@example.com");
+ try {
+ restTemplate.postForEntity(baseUrl, sameUser, User.class);
+ } catch (HttpClientErrorException ex) {
+ assertEquals(HttpStatus.CONFLICT, ex.getStatusCode());
+ }
+ }
+
+ @Test
+ public void getUserById_ShouldReturn200_WhenUserExists() {
+ // First create a user
+ User newUser = new User(null, "Jane", "Doe", "jane.doe@example.com");
+ ResponseEntity response = restTemplate.postForEntity(baseUrl, newUser, User.class);
+
+ String id = response.getBody().getId().toString();
+ // Now get the user by ID
+ ResponseEntity responseEntity = restTemplate.getForEntity(baseUrl + "/{id}", User.class, id);
+
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ assertNotNull(responseEntity.getBody());
+ assertEquals("Jane", responseEntity.getBody().getFirstName());
+ }
+
+ @Test
+ public void updateUser_ShouldReturn200_WhenUserIsUpdatedSuccessfully() {
+ // First create a user
+ User newUser = new User(null, "Jack", "Sparrow", "jack.sparrow@example.com");
+ ResponseEntity response = restTemplate.postForEntity(baseUrl, newUser, User.class);
+ String id = response.getBody().getId().toString();
+
+ // Update user details
+ User updatedUser = new User(id, "Captain", "Sparrow", "captain.sparrow@example.com");
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ HttpEntity entity = new HttpEntity<>(updatedUser, headers);
+
+ ResponseEntity responseEntity = restTemplate.exchange(baseUrl + "/{id}", HttpMethod.PUT, entity, User.class, id);
+
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ assertNotNull(responseEntity.getBody());
+ assertEquals("Captain", responseEntity.getBody().getFirstName());
+ }
+
+ @Test
+ public void deleteUser_ShouldReturn204_WhenUserIsDeletedSuccessfully() {
+ // First create a user
+ User newUser = new User(null, "Will", "Turner", "will.turner@example.com");
+ ResponseEntity response = restTemplate.postForEntity(baseUrl, newUser, User.class);
+ String id = response.getBody().getId().toString();
+
+ // Delete the user
+ restTemplate.delete(baseUrl + "/{id}", id);
+
+ try {
+ restTemplate.getForEntity(baseUrl + "/{id}", User.class, "3");
+ } catch (HttpClientErrorException ex) {
+ assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode());
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/TestUtils.java b/src/test/java/TestUtils.java
new file mode 100644
index 00000000..bc6dc1bc
--- /dev/null
+++ b/src/test/java/TestUtils.java
@@ -0,0 +1,5 @@
+import org.p0.calendly.models.User;
+import org.springframework.http.ResponseEntity;
+
+public class TestUtils {
+}
diff --git a/src/test/java/UnitTests/UserServiceTest.java b/src/test/java/UnitTests/UserServiceTest.java
new file mode 100644
index 00000000..32bb38f3
--- /dev/null
+++ b/src/test/java/UnitTests/UserServiceTest.java
@@ -0,0 +1,89 @@
+package org.p0.calendly.services;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.p0.calendly.exceptions.UserAlreadyExistsException;
+import org.p0.calendly.exceptions.UserNotFoundException;
+import org.p0.calendly.models.User;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class UserServiceTest {
+
+ private UserService userService;
+ private User testUser;
+
+ @BeforeEach
+ void setUp() {
+ userService = new UserService();
+ testUser = new User(null, "John", "Doe", "john.doe@example.com");
+ }
+
+ @Test
+ void testCreateUser() {
+ User createdUser = userService.createUser(testUser);
+ assertNotNull(createdUser);
+ assertNotNull(createdUser.getId());
+ assertEquals("John", createdUser.getFirstName());
+ assertEquals("Doe", createdUser.getLastName());
+ assertEquals("john.doe@example.com", createdUser.getEmail());
+ }
+
+ @Test
+ void testCreateUser_ThrowsUserAlreadyExistsException() {
+ User sameUser = userService.createUser(testUser);
+ assertThrows(UserAlreadyExistsException.class, () -> {
+ userService.createUser(sameUser);
+ });
+ }
+
+ @Test
+ void testGetUserById() {
+ User user = userService.createUser(testUser);
+ User fetchedUser = userService.getUserById(user.getId());
+ assertNotNull(fetchedUser);
+ assertEquals("John", fetchedUser.getFirstName());
+ }
+
+ @Test
+ void testGetUserById_ThrowsUserNotFoundException() {
+ assertThrows(UserNotFoundException.class, () -> {
+ userService.getUserById("999");
+ });
+ }
+
+ @Test
+ void testUpdateUser() {
+ User user = userService.createUser(testUser);
+ testUser.setFirstName("Jane");
+ testUser.setLastName("Smith");
+ testUser.setEmail("jane.smith@example.com");
+
+ User updatedUser = userService.updateUser(user.getId(), testUser);
+
+ assertNotNull(updatedUser);
+ assertEquals("Jane", updatedUser.getFirstName());
+ assertEquals("Smith", updatedUser.getLastName());
+ assertEquals("jane.smith@example.com", updatedUser.getEmail());
+ }
+
+ @Test
+ void testUpdateUser_ThrowsUserNotFoundException() {
+ testUser.setFirstName("Jane");
+
+ assertThrows(UserNotFoundException.class, () -> {
+ userService.updateUser("99", testUser);
+ });
+ }
+
+ @Test
+ void testDeleteUser() {
+ User user = userService.createUser(testUser);
+ String userId = user.getId();
+ userService.deleteUser(userId);
+
+ assertThrows(UserNotFoundException.class, () -> {
+ userService.getUserById(userId);
+ });
+ }
+}
\ No newline at end of file