```table-of-contents title: List of Contents style: nestedOrderedList # TOC style (nestedList|nestedOrderedList|inlineFirstLevel) minLevel: 1 # Include headings from the specified level maxLevel: 1 # Include headings up to the specified level include: exclude: includeLinks: true # Make headings clickable hideWhenEmpty: false # Hide TOC if no headings are found debugInConsole: false # Print debug info in Obsidian console ```
# REST API General Tips ## HTTP Status Messages HTTP response status codes indicate whether a specific HTTP request has been successfully completed. Responses are grouped in five classes: 1. **Informational responses (100 – 199)** 2. **Successful responses (200 – 299)** 3. **Redirection messages (300 – 399)** 4. **Client error responses (400 – 499)** 5. **Server error responses (500 – 599)** Often used codes in excercises are: | Code | Name | Purpose | | ------- | ---------------- | --------------------------------------------------------------------------------------------------------------------- | | **2xx** | **Successful** | | | 200 | OK | The request is OK (this is the standard response for successful HTTP requests) | | 201 | Created | The request has been fulfilled, and a new resource is created | | 204 | No Content | The request has been successfully processed, but is not returning any content (may be returned for successful delete) | | **4xx** | **Client Error** | | | 400 | Bad Request | The request cannot be fulfilled due to bad syntax or content | | 404 | Not Found | The requested page/resource could not be found | If you don't know an HTTP Code you can use `HttpStatus..value()` class in Java For example: `HttpStatus.OK.value()` returns `200`
# REST API in Java with Spring - **Java reference:** https://www.baeldung.com/get-started-with-java-series - **Java reference:** https://docs.oracle.com/en/java/javase/22/ - **Spring Boot:** https://www.baeldung.com/spring-boot - **Spring Boot:** https://spring.io/projects/spring-boot ## Making REST API Requests ### With ***WebClient*** object: (e.g Spring WebFlux) #### GET ```java public void getAllPersons(CustomerSortingOptions sortingOptions, Consumer> customerConsumer) { webClient.get() .uri(uriBuilder -> uriBuilder .path("customers") .queryParam("sortField", sortingOptions.getSortField()) .queryParam("sortingOrder", sortingOptions.getSortingOrder()) .build()) .retrieve() .bodyToMono(new ParameterizedTypeReference>() { }) .onErrorStop() .subscribe(newPersons -> { customers.clear(); customers.addAll(newPersons); customerConsumer.accept(customers); }); } ``` #### POST ```java public void addPerson(Customer customer, Consumer> customerConsumer) { // TODO Part 2: Complete the http post request to the server webClient.post() .uri("customers") .bodyValue(customer) .retrieve() .bodyToMono(Customer.class) .onErrorStop() .subscribe(newCustomer -> { customers.add(newCustomer); customerConsumer.accept(customers); }); } ``` #### PUT ```java public void updatePerson(Customer customer, Consumer> customerConsumer) { // TODO Part 2: Complete the http put request to the server webClient.put() .uri("customers/" + customer.getId()) .bodyValue(customer) .retrieve() .bodyToMono(Customer.class) .onErrorStop() .subscribe(newCustomer -> { customers.replaceAll(x -> x.getId().equals(customer.getId()) ? newCustomer : x); customerConsumer.accept(customers); }); } ``` #### DELETE ```java public void deletePerson(Customer customer, Consumer> customerConsumer) { // TODO Part 2: Make an http delete request to the server webClient.delete() .uri("customers/" + customer.getId()) .retrieve() .toBodilessEntity() .onErrorStop() .subscribe(deletedCustomer -> { customers.remove(customer); customerConsumer.accept(customers); }); } ``` #### Just in case: webClient Constructor ```java public CustomerController() { this.webClient = WebClient.builder() .baseUrl("http://localhost:8080/") .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build(); this.customers = new ArrayList<>(); } ``` #### With Request Parameters: Endpoint: ```java @GetMapping("persons") public ResponseEntity> getAllPersons(PersonSortingOptions sortingOptions) { // Don't use @RequestParams unless object is simple(for example String), for more complex objects Spring parses the object by itself List persons = personService.getAllPersons(sortingOptions); return ResponseEntity.ok(persons); } ``` Request: ```java public void getAllPersons(PersonSortingOptions sortingOptions, Consumer> personsConsumer) { this.webClient.get() .uri(uriBuilder -> uriBuilder // Use uriBuilder to build the path .path("persons") .queryParam("sortingOrder",sortingOptions.getSortingOrder()) // The fields are passed separately .queryParam("sortField",sortingOptions.getSortField()) // In the format: ("Name",value) .build() ) .retrieve() .bodyToMono(new ParameterizedTypeReference>() { }) .onErrorStop() .subscribe(persons -> { this.persons = persons; personsConsumer.accept(this.persons); }); } ``` ### With *RestTemplate*: ```java GetMapping("/getTimeLine/{id}") public List getTimeLine(@PathVariable("id") int userID) { headers.setContentType(MediaType.APPLICATION_JSON); ResponseEntity> response = restTemplate.exchange( getActivityURL + userID, // Set the uri HttpMethod.GET, null, // requestEntity = null as there is no body, new ParameterizedTypeReference>(){} // response body type ); return response.getBody(); } ``` ## REST API Endpoints Example Java class: ```java @RestController @RequestMapping("/activity") public class ActivityController { private Map> userActivityMap = new HashMap<>(); private Map> userFollowedMap = new HashMap<>(); @GetMapping("/getActivity/{id}") public List getActivity(@PathVariable("id") int userID) { List userTweets = userActivityMap.get(userID); if(userTweets == null) return new ArrayList<>(); return userTweets; } @PostMapping("/addActivity") public void addActivity(@RequestBody Tweet tweet) { userActivityMap.computeIfAbsent(tweet.getUser().getUserID(), x -> new LinkedList<>()); userActivityMap.get(tweet.getUser().getUserID()).add(tweet); } @PostMapping("/addFollower") public void addFollower(@RequestBody FollowRequest followRequest) { User follower = followRequest.getFollower(); User followed = followRequest.getFollowed(); userFollowedMap.computeIfAbsent(follower.getUserID(),x -> new LinkedList<>()) .add(followed); } @DeleteMapping("/deleteActivity") public void deleteActivity(@RequestBody Tweet tweet) { List userTweets = userActivityMap.get(tweet.getUser().getUserID()); if(userTweets == null) return; userTweets.remove(tweet); } @DeleteMapping("/deleteFollower") public void deleteFollower(@RequestBody FollowRequest followRequest) { User follower = followRequest.getFollower(); User followed = followRequest.getFollowed(); List followedUsers = userFollowedMap.get(follower.getUserID()); if(followedUsers == null) return; followedUsers.remove(followed); } @GetMapping("/getFollowedList/{id}") public List getFollowedList(@PathVariable("id") int userID) { // TODO: this method is also not complete, so you have to implement it accordingly :) List followed = userFollowedMap.get(userID); if(followed == null) return new ArrayList<>(); return followed; } } ``` ## Excersises: - L02PE01 TUM Social App - L02PE02 REST Architectural Style Part 2 - L09PE02 Wizardry Character Creation
# gRPC - **Java reference:** https://www.baeldung.com/get-started-with-java-series - **Java reference:** https://docs.oracle.com/en/java/javase/22/ - **gRPC reference**: https://grpc.io/docs/ - **gRPC Java basics**: https://grpc.io/docs/languages/java/basics/ ## Protobuf files #### Defining classes: Important tips: * Array in protobuf means ```repeated``` field ```protobuf message Date { int32 day = 1; // Number specifies order int32 month = 2; int32 year = 3; } message Location { string city = 1; string country = 2; } message LocationDate { Location location = 1; Date date = 2; } ``` #### Defining services: ```protobuf service WeatherReporter { //Returning one object rpc GetCityWeatherSingleDay(LocationDate) returns (CityWeatherData) {} // Returning a stream of objects rpc GetCityWeatherMultipleDays(LocationDatePeriod) returns (stream CityWeatherData) {} } ``` #### Example of services and objects with **repeated** fields This will be utilized by some of the client side examples underneath ```protobuf service MetadataService { rpc getTrackMetadata(EntitiesRequest) returns (Tracks) {} rpc getAlbumMetadata(EntitiesRequest) returns (Albums) {} } message EntitiesRequest { repeated uint32 id = 1; } message Track { uint64 id = 1; string name = 2; string album = 3; string artist = 4; uint32 duration = 5; } message Tracks { repeated Track track = 1; } message Album { uint64 id = 1; string name = 2; string artist = 3; repeated uint32 tracks = 4; } message Albums { repeated Album album = 1; } ``` #### Generating classes from *.proto* files 1. Gradle tab in IDE and press "Tasks > other > generateProto") 2. The generated classes from the proto file should be in build/generated/source/proto/main folder ## Server side In practice can be done with streams or with simple iteration ```java public void getCityWeatherSingleDay(LocationDate request, StreamObserver responseObserver) { var data = allWeatherData.stream().filter(cityWeatherData -> cityWeatherData.getLocationDate().equals(request)).findFirst().orElse(null); if(data != null)responseObserver.onNext(data); responseObserver.onCompleted(); } ``` #### For returning a *stream* of data just call onNext multiple times With iteration: ```Java public void getCityWeatherMultipleDays(LocationDatePeriod request, StreamObserver responseObserver) { for(CityWeatherData data : allWeatherData) { if(data.getLocationDate().getLocation().equals(request.getLocation())) { if(compareDate(data.getLocationDate().getDate(), request.getStartDate()) >= 0 && compareDate(data.getLocationDate().getDate(), request.getEndDate()) <= 0) { responseObserver.onNext(data); } } } responseObserver.onCompleted(); } ``` With streams: ```java public void getCityWeatherMultipleDays(LocationDatePeriod request, StreamObserver responseObserver) { var startDate = request.getStartDate(); var endDate = request.getEndDate(); allWeatherData.stream() .filter(cityWeatherData -> cityWeatherData.getLocationDate().getLocation().equals(request.getLocation())) .filter(cityWeatherData -> compareDate(cityWeatherData.getLocationDate().getDate(),startDate) >= 0) .filter(cityWeatherData -> compareDate(cityWeatherData.getLocationDate().getDate(),endDate) <= 0) .forEach(responseObserver::onNext); responseObserver.onCompleted(); } ``` #### For returning an object which contains array of other objects (stupid???) ```java public void getAlbumMetadata(EntitiesRequest request, StreamObserver responseObserver) { Albums.Builder albumsBuilder = Albums.newBuilder(); for(int i = 0; i < request.getIdCount();i++) { // Find the album by its id Album album = database.findAlbumById(request.getId(i)); if(album != null) { albumsBuilder.addAlbum(album); } } // In case that an album isn't found builder builds empty list Albums albums = albumsBuilder.build(); responseObserver.onNext(albums); responseObserver.onCompleted(); } ``` #### If you want to return an error ```java public void getTrafficData(RouteRequest request, StreamObserver responseObserver) { var trafficModel = db.getTrafficModel(request); var response = trafficModel.query(request); if(response.isPresent()) { responseObserver.onNext(response.get()); responseObserver.onCompleted(); } else { // Just pass an exception with the onError method responseObserver.onError(new Exception("No data available.")); } } ``` ## Client Side #### Setup The **blocking stub** is a placeholder on which functions are called. It needs to be received from the gRPC object in some way. ```java public WeatherClient(Channel channel) { // Get blocking stub from the gRPC object blockingStub = WeatherReporterGrpc.newBlockingStub(channel); } ``` or ```java public SpotifyClient(Channel channel) { // spotifyConnection is a wrapper defined in the excercise // this is just an example that there can be other ways to get the stub this.spotifyConnection = new SpotifyConnection(channel); this.blockingStub = spotifyConnection.getStub(); } ``` #### Calling method with ***single object*** return value ```java public void getCityWeatherSingleDay(int day, int month, int year, String city, String country) { // Need to use builders to create the objects for the request Date date = Date.newBuilder().setDay(day).setMonth(month).setYear(year).build(); Location location = Location.newBuilder().setCity(city).setCountry(country).build(); // Building the locationDate which will be passed as a parameter LocationDate locationDate = LocationDate.newBuilder().setDate(date).setLocation(location).build(); // Getting the returned object from calling the method on the stub CityWeatherData cityWeatherData = blockingStub.getCityWeatherSingleDay(locationDate); System.out.println(cityWeatherData.toString()); } ``` #### Calling method which returns ***stream*** ```java public void getCityWeatherMultipleDays(int startDay, int startMonth, int startYear, int endDay, int endMonth, int endYear, String city, String country) { // Building objects for the request Date startDate = Date.newBuilder() .setDay(startDay) .setMonth(startMonth) .setYear(startYear) .build(); Date endDate = Date.newBuilder() .setDay(endDay) .setMonth(endMonth) .setYear(endYear) .build(); Location location = Location.newBuilder().setCity(city).setCountry(country).build(); // Building LocationDatePeriod object to be passed as parameter LocationDatePeriod locationDatePeriod = LocationDatePeriod.newBuilder() .setStartDate(startDate) .setEndDate(endDate) .setLocation(location) .build(); // Method call returns Iterator for the stream of CityWeatherData Iterator iterator = blockingStub.getCityWeatherMultipleDays(locationDatePeriod); // Iterating over the objects while (iterator.hasNext()) { CityWeatherData cityWeatherData = iterator.next(); System.out.println(cityWeatherData.toString()); } } ``` #### Calling method which has *parameter with **repeated** field* ```java public String displayPlaylist(int[] playlist) throws SpotifyClientException { StringBuilder stringBuilder = new StringBuilder(); EntitiesRequest.Builder builder = EntitiesRequest.newBuilder(); // IMPORTANT for REPEATED fields // Add all id's to request for(int id : playlist) { builder.addId(id); } EntitiesRequest request = builder.build(); // Unlike in earlier examplese returns OBJECT which contains multiple Tracks Tracks tracks; tracks = blockingStub.getTrackMetadata(request); if(tracks.getTrackCount() != playlist.length) throw new SpotifyClientException("Failed to get track metadata"); for(int i = 0; i < tracks.getTrackCount();i++) { Track track = tracks.getTrack(i); int durationInSeconds = track.getDuration(); int durationInMinutes = durationInSeconds / 60; durationInSeconds = durationInSeconds % 60; stringBuilder.append(String.format("%d. %s - %s (%d:%02d)%n", i+1,track.getName(), track.getArtist(), durationInMinutes, durationInSeconds)); } return stringBuilder.toString(); } ``` ## Excersises: - L02PB01 Weather Reporter - L02P01 Spotify gRPC - L03PE03 Gobblemaps # Flask API in Python - **Python reference:** https://docs.python.org/3/reference/index.html ## *Server side* endpoints #### GET ```python @api.route("/", methods=["GET"]) def get(key: str) -> Response: """ Retrieve the file-content associated with the given key owned by the user. If there is no user-id `uid` given in the request body or the user-id does not match, return error message and status. Otherwise, return the file-content and success status. """ request_body: dict = request.json ff: FileFolder = current_app.ff # type: ignore user_id = request_body.get("uid") if user_id is None: return Response(NO_UID_IN_REQUEST_BODY,BAD_REQUEST) file = ff.get(key) if file is None: return Response(response=KEY_DOES_NOT_EXIST % key, status=NOT_FOUND) if file.uid != user_id: return Response(response=NO_ACCESS_TO_ENTRY % key, status=BAD_REQUEST) return Response(response=file.content,status=OK) pass ``` #### PUT ```python @api.route("/", methods=["PUT"]) def put(key: str) -> Response: """ Upsert a file-content associated with the given key owned by the user. If there is no user-id `uid` or file-content `content` given in the request body, return error message and status. Otherwise, return the success message and success status """ request_body: dict = request.json ff: FileFolder = current_app.ff if "uid" not in request_body: return Response(NO_UID_IN_REQUEST_BODY,BAD_REQUEST) if "content" not in request_body: return Response(NO_CONTENT_IN_REQUEST_BODY,BAD_REQUEST) uid = request_body["uid"] file_content = request_body["content"] old_entry = ff.get(key) if old_entry is None: entry = Entry(uid,file_content) success = ff.put(key,entry) if success: return Response(PUT_SUCCESS,OK) else: return Response(NO_SPACE_IN_FILE_FOLDER,BAD_REQUEST) if uid != old_entry.uid: return Response(NO_ACCESS_TO_ENTRY % key,status=BAD_REQUEST) entry = Entry(uid,file_content) ff.put(key,entry) return Response(PUT_SUCCESS,OK) pass ``` #### DELETE ```python @api.route("/", methods=["DELETE"]) def remove(key: str) -> Response: """ Remove the file-content associated with the given key owned by the user. If there is no user-id `uid` given in the request body or the user-id does not match, return error message and status. Otherwise, return the removed file-content and success status """ request_body: dict = request.json ff: FileFolder = current_app.ff if "uid" not in request_body: return Response(NO_UID_IN_REQUEST_BODY,BAD_REQUEST) uid = request_body["uid"] entry = ff.get(key) if entry is None: return Response(KEY_DOES_NOT_EXIST % key,NOT_FOUND) if entry.uid != uid: return Response(NO_ACCESS_TO_ENTRY % key,BAD_REQUEST) ff.remove(key) return Response(entry.content,OK) pass ``` #### GET - all ```python @api.route("/", methods=["GET"]) def list() -> Response: """ List all (key, file-content) tuples owned by the user. If there is no user-id `uid` given in the request body, return error message and status. Otherwise, return the list of file-content and success status. """ request_body: dict = request.json ff: FileFolder = current_app.ff if "uid" not in request_body: return Response(NO_UID_IN_REQUEST_BODY,BAD_REQUEST) uid = request_body["uid"] items = ff.items() output = [] for key, entry in items: output.append(f"{key}:{entry.content}") output_string = ".".join(output) return Response(output_string,OK) pass ``` ## Making requests on the *client side* #### Helper method ```python # make json request to server def json_request(self, method: str, path: str, body: dict): self.conn.request(method, path, json.dumps(body), headers={"Content-Type": "application/json"}) flask_response = self.conn.getresponse() return flask_response.read().decode() ``` #### PUT ```python def put(self, key, content): """ Upsert (key, content) tuple into the file folder. Return the server response """ request_body = {"uid": self.user_id, "content": content} return self.json_request("PUT", f"/{key}",request_body) pass ``` #### GET ```python def read(self, key): """ Retrieve the file-content associated with the given key owned by the user. Return the server response """ request_body = {"uid": self.user_id} return self.json_request("GET",f"/{key}",request_body) pass ``` #### DELETE ```python def remove(self, key): """ Remove the file-content associated with the given key owned by the user. Return the server response """ request_body = {"uid": self.user_id} return self.json_request("DELETE",f"/{key}",request_body) pass ``` #### GET - ALL ```python def list(self): """ List all file-content owned by the user. Return the server response """ request_body = {"uid": self.user_id} return self.json_request("GET","/",request_body) pass ``` ## Excersises: - L03PE01 Online FileFolder # Python - general syntax and tips - **Python reference:** https://docs.python.org/3/reference/index.html ## Dictonaries Dictionaries are initialized with `{}` ```python thisdict = {   "brand": "Ford",   "model": "Mustang",   "year": 1964 } print(thisdict["brand"]) ``` ```python thisdict = dict(name = "John", age = 36, country = "Norway") print(thisdict) ``` You can check if a key exists with the keyword *in*: ```python if "model" in thisdict:   print("Yes, 'model' is one of the keys in the thisdict dictionary") ``` You can loop over keys and values in dictionary: ```python for x in thisdict: # Loop over keys   print(x) for x in thisdict.keys(): #Another way to loop over keys   print(x)   for x in thisdict: # Loop over values   print(thisdict[x]) for x in thisdict.values(): # Another way to loop over values   print(x)   for x, y in thisdict.items(): # Looping over key-value pairs as tuples   print(x, y) ``` Dictonaries are of type `dict` ### Dictionary methods | Method | Description | | ------------ | ----------------------------------------------------------------------------------------------------------- | | clear() | Removes all the elements from the dictionary | | copy() | Returns a copy of the dictionary | | fromkeys() | Returns a dictionary with the specified keys and value | | get() | Returns the value of the specified key | | items() | Returns a list containing a tuple for each key value pair | | keys() | Returns a list containing the dictionary's keys | | pop() | Removes the element with the specified key | | popitem() | Removes the last inserted key-value pair | | setdefault() | Returns the value of the specified key. If the key does not exist: insert the key, with the specified value | | update() | Updates the dictionary with the specified key-value pairs | | values() | Returns a list of all the values in the dictionary | ## Lists Lists are initialized with `[]` ```python mylist = ["apple", "banana", "cherry"] ``` List items are ordered, changeable, and allow duplicate values. List items are indexed, the first item has index `[0]`, the second item has index `[1]` etc. ```python print mylist[0] ``` You can also negatively index a list from the end: ```python print mylist[-1] # This refers to the last item ``` To determine how many items a list has, use the `len()` function: ```python thislist = ["apple", "banana", "cherry"] print(len(thislist)) ``` You can loop through lists like so: ```python thislist = ["apple", "banana", "cherry"] for x in thislist:   print(x) ``` ### Joining lists You can easily join list using the `+` operator: ```python list1 = ["a", "b", "c"] list2 = [1, 2, 3] list3 = list1 + list2 print(list3) ``` Or you can iterate through one of the lists and append the elements **one by one**: ```python list1 = ["a", "b" , "c"] list2 = [1, 2, 3] for x in list2:   list1.append(x) print(list1) ``` Another way is to use the `extend()` function: ```python list1 = ["a", "b" , "c"] list2 = [1, 2, 3] list1.extend(list2) print(list1) ``` List comprehension is like **Streams in Java** and offers a functional way to iterate over lists ```python fruits = ["apple", "banana", "cherry", "kiwi", "mango"] # Creates a new list from fruits which contains only the elements which contain "a" newlist = [x for x in fruits if "a" in x] print(newlist) ``` Lists are of type `list` Other methods that you can use on lists are: | Method | Description | | --------- | ---------------------------------------------------------------------------- | | append() | Adds an element at the end of the list | | clear() | Removes all the elements from the list | | copy() | Returns a copy of the list | | count() | Returns the number of elements with the specified value | | extend() | Add the elements of a list (or any iterable), to the end of the current list | | index() | Returns the index of the first element with the specified value | | insert() | Adds an element at the specified position | | pop() | Removes the element at the specified position | | remove() | Removes the item with the specified value | | reverse() | Reverses the order of the list | | sort() | Sorts the list | ## Strings Strings in python are surrounded by either single quotation marks, or double quotation marks. 'hello' is the same as "hello". Strings are of class `string` You can access a character of a string like accessing an array or list ```python a = "Hello, World!" print(a[1]) # This will print "e" ``` You can loop through a string: ```python for x in "banana":   print(x) ``` You can get the length of a string: ```python a = "Hello, World!" print(len(a)) ``` To check if a string contains another string: ```python txt = "The best things in life are free!" if "free" in txt:   print("Yes, 'free' is present.") ``` You can *slice* or get substrings from a string like so: ```python b = "Hello, World!" print(b[2:5]) # From 2(inclusive) to 5(not inclusive) print(b[:5]) # From 0 to 5(not inclusive) print(b[2:]) # Fromt 2(inclusive) to end ``` ### Modifying strings The `upper()` method converts to uppercase ```python a = "Hello, World!" print(a.upper()) # "HELLO, WORLD!" ``` The `lower()` method converts to lowercase ```python print(a.lower()) # "hello, world!" ``` The `strip()` method removes any whitespace from the beginning or the end: ```python a = " Hello, World! " print(a.strip()) # returns "Hello, World!" ``` The `replace()` method replaces a string with another string: ```python a = "Hello, World!" print(a.replace("H", "J")) ``` The `split()` method splits the string into substrings if it finds instances of the separator: ```python a = "Hello, World!" print(a.split(",")) # returns ['Hello', ' World!'] ``` You can concatenate strings simply with `+` ### Format strings F-strings allows you to format selected parts of a string. To specify a string as an f-string, simply put an `f` in front of the string literal, like this: To format values in an f-string, add placeholders `{}`, a placeholder can contain variables, operations, functions, and modifiers to format the value. ```python price = 59 txt = f"The price is {price} dollars" print(txt) ``` You can add modifiers like .2f for two decimals ```python price = 59 txt = f"The price is {price:.2f} dollars" print(txt) ```
# JUnit - **Java reference:** https://www.baeldung.com/get-started-with-java-series - **Java reference:** https://docs.oracle.com/en/java/javase/22/ - **JUnit:** https://junit.org/junit5/docs/current/user-guide/ ## assertThrows() ```java @Test public void testSetOnlineCourseUrlWithInvalidUrl() { OnlineCourse course = new OnlineCourse("test"); // You need to pass the CLASS of the exception and a runnable which calls the function // then the test will pass if EXACTLY this exception is throwed // If you want to test for any exception pass Exception.class however, that is usually discouraged and not needed assertThrows(MalformedURLException.class,() -> course.setUrl("hjdahjsd")); } ``` ## assertDoesNotThrow() ```java @Test public void testSetOnlineCourseUrlWithValidUrl(){ OnlineCourse course = new OnlineCourse("test"); // Simply assertDoesNotThrow with a runnable assertDoesNotThrow(() -> course.setUrl("https://test")); // We need to handle constructor of URL throwing an exception despite knowing that it won't as we are passing it a correct URL try { assertEquals(new URL("https://test"),course.getUrl()); } catch (MalformedURLException e) { } } ``` ## Excercises: - L06P01 University App Unit Testing Part 2 - L06PB02 Chris Neapolitan Restaurant - L06PB01 University App Unit Testing - L06PE01 The Iron Bank of Braavos # EasyMock - **Java reference:** https://www.baeldung.com/get-started-with-java-series - **Java reference:** https://docs.oracle.com/en/java/javase/22/ - **EasyMock:** https://easymock.org/user-guide.html `@TestSubject` annotations automatically calls the constructor with the mockObjects. However it is not applicable for constructors with other parameters that are not mocked(for example primitive types). In such cases you can call the constructor of the **test subject** as usual inside the test or in a @Before method or wherever you want. **Second example contains how to expect *void* method calls** ## Example with TestSubject ```java // Needs to extend the tests with EasyMock // If not extended you would need to manually initialize Mock objects @ExtendWith(EasyMockExtension.class) class DiscussionTest { // TestSubject annotations automatically calls the constructor with the mockObjects // However it is not applicable for constructors with other parameters @TestSubject private Discussion discussion; // Mock objects need to be annotated with @Mock @Mock private Course courseMock; @Mock private Comment commentMock; // TODO implement the tests @Test public void testComment() { // First we need to record // Expect a function call and set a predetermined return value // This expects the method to be called exactly once expect(commentMock.save()).andReturn(true); // After setting all expectations we need to replay the mock - e.g set it so it behaves the way we recorded replay(commentMock); // Test your objects as usual discussion = new Discussion(); assertEquals(0,discussion.getNumberOfComments()); assertTrue(discussion.addComment(commentMock)); assertEquals(1,discussion.getNumberOfComments()); // At the end you need to verify the mock - this checks whether the expected methods were actually called verify(commentMock); } // The methods below show the same usage @Test public void testCommentIfSavingFails() { expect(commentMock.save()).andReturn(false); replay(commentMock); discussion = new Discussion(); assertEquals(0,discussion.getNumberOfComments()); assertFalse(discussion.addComment(commentMock)); assertEquals(0,discussion.getNumberOfComments()); verify(commentMock); } @Test public void testStartCourseDiscussion() { Person person = new Lecturer("","",LocalDate.now()); String testTopic = "testTopic"; expect(courseMock.isDiscussionAllowed(person)).andReturn(true); replay(courseMock); assertTrue(discussion.startCourseDiscussion(courseMock,person,testTopic)); assertEquals(courseMock,discussion.getCourse()); assertEquals(testTopic,discussion.getTopic()); verify(courseMock); } ``` ## Example with custom Constructor (also contains expecting *void* call) ```java @Mock Platform platformMock; ... @Test public void addNoteGetsURIFromPlatform() throws URISyntaxException { URI uri = URI.create("https://notes.tum.de/examplePath"); // An example uri your mock can return String expectedWriteDataPath = uri.getPath(); // The path that the NotesServer should write to // Set the mock to expect the call and return the set URI expect(platformMock.nextURI()).andReturn(uri); // To expect a void method call you can just call the method like usual before **replaying** the mock platformMock.writeData(expectedWriteDataPath,"Some text"); // Replay the mock replay(platformMock); // Instead of annotating with @TestSubject we instantiate the testServer(System Under Test) inside the Test NotesServer testServer = new NotesServer(platformMock, false, false); // Test like usual URI testUri = testServer.addNote("alice", "Some text"); assertEquals(expectedWriteDataPath,testUri.getPath()); // At the end you need to verify the mock - this checks whether the expected methods were actually called verify(platformMock); } ``` ## If you want multiple method invocations | Method | Behaviour | | ---------------- | -------------------------------------------------------------------------------------- | | `.times(3)` | Expects the method to be called **exactly** 3 times. | | `.times(2,5)` | Expects the method to be called between **2 and 5** times (inclusive). | | `.atLeastOnce()` | Expects the method to be called **1 or more** times. | | `.anyTimes()` | The method can be called **0 or more** times (it will never cause `verify()` to fail). | For *void* methods: ```java commentMock.update(); // Call the void method expectLastCall().times(3); // Set the invocation count ``` ### If you want to check that a method is NOT called just replay without expect() ```java @Test void testSubmitAbsenceDoesNotAlertInstructor() { /** * TODO: Mock mailService.sendMail(...) to ensure that the instructor is not alerted * Tip: By not specifying the behaviour of the mock you can afterwards verify if a method was NOT called * Don't forget to use replay! */ Person person = personBuilder .withFirstName("Max") .withLastName("Mustermann") .withIsInstructor(false) .build(); personService.save(person); Person instructor = personBuilder .withFirstName("Ivailo") .withLastName("Traikov") .withIsInstructor(true) .build(); personService.save(instructor); List instructors = personService.getInstructors(); Absence absence = new Absence(); absence.setPerson(person); absence.setComment("test"); LocalDate now = LocalDate.now(); absence.setDate(now); absence.setId(Long.valueOf(1)); replay(mailServiceMock); absenceService.submitAbsence(absence,false); verify(mailServiceMock); } ``` ## Excercises: - L06PB02 Chris Neapolitan Restaurant - L06P02 Discussion Forum Mock Testing
# Static and dynamic analysis - **C reference:** https://en.cppreference.com/w/c/language - **AddressSanitizer**: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html ## Static analysis ### Gcc With `-Wall`, `-Wextra` compiler flags ### Cmake With `clang --analyze` ## Dynamic Analysis ### Undefined Behavior Sanitizer Deals with undefined behaviour in the program such as **Integer overflow** To use Undefined Behaviour Sanitizer **compile** with `-fsanitize=undefined` It is available both on `gcc` and `clang` ```bash gcc -fsanitize=undefined -o ``` ### Address Sanitizer **C Address Safety errors that can be caught by address sanitizer:** - **Buffer Overflow**: This occurs when a program writes data beyond the bounds of allocated memory. It can lead to data corruption, crashes, and even allow attackers to exploit the program. - **Use-Stack-After-Return**: This involves accessing memory after it has been freed. It can lead to undefined behavior as the freed memory might be allocated again for a different purpose. - **Double Free**: Calling `free()` twice on the same value can lead to undefined behavior. When a program calls `free()` twice with the same argument, the program’s memory management data structures become corrupted and could allow a malicious user to write values in arbitrary memory spaces. Address Sanitizer is available on `gcc` and `clang` To use Address Sanitizer compile with `-fsanitize=address` ```bash clang -fsanitize=address -w -lpthread -o ``` ### Memory Sanitizer **Memory sanitizer specifically targets uninitialized memory reads** It is only available on `clang` with the flag `-fsanitize=memory` ```bash clang -fsanitize=memory -o ``` ### Thread Sanitizer **Thread Sanitizer deals specifically with data races** It is available on both `gcc` and `clang` with the flag `-fsanitize=thread` ```bash gcc -fsanitize=thread -o ``` In general to use multi threading in C you need to use the `-pthread` flag. So if you don't see it you may need to add that as well. ## Tips while doing dynamic analysis: To test for **buffer overflow** you can try entering **too long inputs**, **going out of boundaries of lists, maps and such** In general try testing every function while you are playing around with the program. **Buffer overflows** caused by **`scanf()`** can be fixed with **`fgets()`** This however does not **zero-terminate** the string so you have to do that manually ```C char firstName[NAME_LENGTH]; printf("Enter first name: "); fgets(firstName,NAME_LENGTH,stdin); lastName[NAME_LENGTH-1] = 0; ``` ## Excercises: - L09P02 Garching Best Software - L08PE04 Spaceship - L07P02 Dynamic Code analysis for Priority Scheduling - L07PE02 Playlist Manager -- Memory Debugging with MemorySanitizer - L07PE03 Gym Threading
# C Thread Synchronization primitives - **C reference:** https://en.cppreference.com/w/c/language ## Mutex The mutex is a simple lock that after locking blocks other threads from accessing the object/code until it is unlocked. If a thread tries to lock the mutex while it is already locked by another it must wait until it is unlocked again. ### Type Mutexes in C are of type `pthread_mutex_t` and can be declared like so: ```C struct Shower { pthread_mutex_t showerLock; }; ``` ### Initialization Before they can be used they also need to be initialized ```C struct Shower s;; pthread_mutex_init(&s.showerLock,NULL); ``` The second parameter is a mutex attribute which determines what kind of mutex it is. The kind of a mutex determines whether it can be locked again by a thread that already owns it. **For our purposes not specifying is okay and you can pass NULL.** ### Locking and unlocking To lock a mutex call `pthread_mutex_lock()` and to unclock it `pthread_mutex_unlock()` ```C void useShower(struct Shower *s, struct Member m) { pthread_mutex_lock(&s->showerLock); ///////////////////////////////// logShower(1, m); usleep(rand() % 500000); // Member uses shower logShower(0, m); ///////////////////////////////// pthread_mutex_unlock(&s->showerLock); } ``` ## Semaphores Semaphores are counters which only let a fixed number of threads to access the shared resource at the same time. To enter the thread needs to acquire a token with `sem_wait()`, when one is not available it waits. A thread also needs to return the acquired token by calling `sem_post()` ### Type Semaphores in C are of type `sem_t` and can be declared like so: ```C struct ChangingRoom { sem_t changingRoomSemaphore; }; ``` ### Initialization Before you can use a semaphore you need to initialize it `int sem_init(sem_t *sem, int pshared, unsigned int value);` `pshared` indicates if this semaphore is shared between threads of a process or between processes - 0 means between threads of a process and is fine for our exercises `value` - how many threads can use the resource at the same time ```C struct ChangingRoom cr; sem_init(&cr.changingRoomSemaphore,0,3); ``` ## Waiting and posting ```C void useChangingRoom(struct ChangingRoom *cr, struct Member m) { sem_wait(&cr->changingRoomSemaphore); //////////////////////////////////////// logChangingRoom(1, m); usleep(rand() % 500000); // Member uses changing room logChangingRoom(0, m); //////////////////////////////////////// sem_post(&cr->changingRoomSemaphore); } ```
# Git - **Git reference:** https://git-scm.com/docs ## git checkout Git checkout has two functions: 1. To change the current branch 2. To restore a version of a specified file ### Switching and creating **branches** If you want to switch to an existing branch you can use: ```bash git checkout ``` If you want to create a new branch and switch to it you can do: ```bash git checkout -b ``` This starts the branch at the current commit. ### Restoring or taking a specific version of a file To restore the file version from a specific commit: ```bash git checkout ``` To restore the file version from a specific branch: ```bash git checkout ``` You can also checkout a whole commit. ```bash git checkout ``` This however creates a detached head state in which you cannot make new commits. It isn't the same as replacing all files in the current directory with the states from the commit. Think of it more as changing to the selected commit like you would change to a branch. If you however want to replace all files in the current directory with the states from the commit ```bash git checkout . ``` ## git merge This merges the selected branch into the current branch. You can change the current branch with `git checkout` Merging is usually done automatically but sometimes that cannot be done because the two branches have changes to the same code which cannot be merged automatically. This is called a merge conflict. When this is the case `git` puts conflict markers in the files which look like this: ```c void calculate_bonus(struct employeeInfo *entry) { //calculation of bonus factor float bonus_factor = 1000 / ( entry->salary); //calculation of grade factor float grade_factor = entry->performanceGrade / 10.0; //calculate_bonus <<<<<<< HEAD // This ist the change from the current branch entry->bonus = entry->salary * bonus_factor * grade_factor * 0.5; ======= entry->bonus = entry->salary * bonus_factor * grade_factor * 2; >>>>>>> feature/new_feature_E // This is the incoming change } ``` To resolve the merge conflict you need to delete the conflict markers and keep the code you desire. You can even chose to resolve the conflict through writing code that isn't included in both of the changes. After that you need to commit the resolved code. If you want to **squash** all commits on another branch and apply them as a **single** commit on the current branch you can use: ```bash git merge --squash ``` ## git rebase Git **rebase** is usually used to take a series of commits and **replay** them on top of another commit. This will take all commits on the current branch and **replay** them on main. Unlike merge it goes in the direction *current commit => main* ```bash git rebase ``` or if you want to select the branch to **replay** ```bash git rebase ``` Git rebase however can also be used to **squash** or **reorder** commits by running it in interactive mode on the last N commits. ```bash git rebase -i HEAD~N ``` This opens this editor: ```pug pick f7f3f6d Change my name a bit pick 310154e Update README formatting and add blame pick a5f4a0d Add cat-file # Rebase 710f0f8..a5f4a0d onto 710f0f8 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop = remove commit # l, label