Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SLVUU-129: Fix Application Layouts Data Contract #135

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/test-ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ jobs:
working-directory: ./vuu-ui
browser: chrome
build: npm run showcase:build
start: npm run showcase:preview
wait-on: "http://localhost:4173"
start: npm run showcase:preview, npm run layout-server
wait-on: http://localhost:4173, http://localhost:8081/api/swagger-ui/index.html
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the Swagger page is most appropriate for checking the API status. We could use one of our GET endpoints, or implement a separate endpoint just for status checking.

- name: Run end-to-end tests in Edge
uses: cypress-io/github-action@bd9dda317ed2d4fbffc808ba6cdcd27823b2a13b
with:
Expand All @@ -50,7 +50,7 @@ jobs:
browser: edge
build: npm run showcase:build
start: npm run showcase:preview
wait-on: "http://localhost:4173"
wait-on: http://localhost:4173, http://localhost:8081/api/swagger-ui/index.html

# ensure the vuu example still builds
vuu-app-build:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import org.finos.vuu.layoutserver.dto.response.ApplicationLayoutDto;
import org.finos.vuu.layoutserver.service.ApplicationLayoutService;
import org.modelmapper.ModelMapper;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -21,7 +19,6 @@
public class ApplicationLayoutController {

private final ApplicationLayoutService service;
private final ModelMapper mapper;

/**
* Gets the persisted application layout for the requesting user. If the requesting user does not have an
Expand All @@ -32,27 +29,29 @@ public class ApplicationLayoutController {
*/
@ResponseStatus(HttpStatus.OK)
@GetMapping
public ApplicationLayoutDto getApplicationLayout(@RequestHeader("username") String username) {
return mapper.map(service.getApplicationLayout(username), ApplicationLayoutDto.class);
public ObjectNode getApplicationLayout(@RequestHeader("username") String username) {
return service.getApplicationLayout(username).getApplicationLayout();
}

/**
* Creates or updates the unique application layout for the requesting user.
*
* @param layoutDefinition JSON representation of the application layout to be created
* @param username the user making the request
* @param applicationLayout JSON representation of all relevant data about the application layout to be created,
* containing top-level nodes for the layout itself, and (optionally) for associated settings
* @param username the user making the request
*/
@ResponseStatus(HttpStatus.CREATED)
@PutMapping
public void persistApplicationLayout(@RequestHeader("username") String username, @RequestBody ObjectNode layoutDefinition) {
service.persistApplicationLayout(username, layoutDefinition);
public void persistApplicationLayout(@RequestHeader("username") String username,
@RequestBody ObjectNode applicationLayout) {
service.persistApplicationLayout(username, applicationLayout);
}

/**
* Deletes the application layout for the requesting user. A 404 will be returned if there is no existing
* application layout.
*
* @param username the user making the request
* @param username the user making the request
*/
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ public class ApplicationLayout {

@Convert(converter = ObjectNodeConverter.class)
@Column(columnDefinition = "JSON")
private ObjectNode definition;
private ObjectNode applicationLayout;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public class ApplicationLayoutService {
private final ApplicationLayoutRepository repository;
private final DefaultApplicationLayoutLoader defaultLoader;

public void persistApplicationLayout(String username, ObjectNode layoutDefinition) {
repository.save(new ApplicationLayout(username, layoutDefinition));
public void persistApplicationLayout(String username, ObjectNode applicationLayout) {
repository.save(new ApplicationLayout(username, applicationLayout));
}

public ApplicationLayout getApplicationLayout(String username) {
Expand Down
47 changes: 27 additions & 20 deletions layout-server/src/main/resources/defaultApplicationLayout.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
{
"id": "main-tabs",
"type": "Stack",
"props": {
"className": "vuuShell-mainTabs",
"TabstripProps": {
"allowAddTab": true,
"allowRenameTab": true,
"animateSelectionThumb": false,
"className": "vuuShellMainTabstrip",
"location": "main-tab"
"applicationLayout": {
"type": "Stack",
"id": "main-tabs",
"props": {
"className": "vuuShell-mainTabs",
"TabstripProps": {
"allowAddTab": true,
"allowCloseTab": true,
"allowRenameTab": true,
"animateSelectionThumb": false,
"location": "main-tab",
"tabClassName": "MainTab"
},
"preserve": true,
"active": 0
},
"preserve": true,
"active": 0
},
"children": [
{
"type": "Placeholder",
"title": "Page 1"
}
]
}
"children": [
{
"props": {
"id": "tab1",
"title": "Tab 1",
"className": "vuuShell-Placeholder"
},
"type": "Placeholder"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,46 @@
package org.finos.vuu.layoutserver.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.finos.vuu.layoutserver.dto.response.ApplicationLayoutDto;
import org.finos.vuu.layoutserver.model.ApplicationLayout;
import org.finos.vuu.layoutserver.service.ApplicationLayoutService;
import org.finos.vuu.layoutserver.utils.ObjectNodeConverter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.modelmapper.ModelMapper;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class ApplicationLayoutControllerTest {
private static ApplicationLayoutService mockService;
private static ApplicationLayoutController controller;
private static final ModelMapper modelMapper = new ModelMapper();
private static final ObjectNodeConverter objectNodeConverter = new ObjectNodeConverter();

@BeforeEach
public void setup() {
mockService = Mockito.mock(ApplicationLayoutService.class);
controller = new ApplicationLayoutController(mockService, modelMapper);
controller = new ApplicationLayoutController(mockService);
}

@Test
public void getApplicationLayout_anyUsername_returnsLayoutFromService() throws JsonProcessingException {
public void getApplicationLayout_anyUsername_returnsLayoutFromService() {
String user = "user";
ObjectNode definition = objectNodeConverter.convertToEntityAttribute("{\"id\":\"main-tabs\"}");

when(mockService.getApplicationLayout(user))
.thenReturn(new ApplicationLayout(user, definition));

ApplicationLayoutDto response = controller.getApplicationLayout(user);
ObjectNode response = controller.getApplicationLayout(user);

assertThat(response.getUsername()).isEqualTo(user);
assertThat(response.getDefinition()).isEqualTo(definition);
assertThat(response).isEqualTo(definition);

verify(mockService, times(1)).getApplicationLayout(user);
}

@Test
public void persistApplicationLayout_anyInput_callsService() throws JsonProcessingException {
public void persistApplicationLayout_anyInput_callsService() {
String user = "user";
ObjectNode definition = objectNodeConverter.convertToEntityAttribute("{\"id\":\"main-tabs\"}");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.iterableWithSize;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

Expand All @@ -49,9 +53,8 @@ public void getApplicationLayout_noLayoutExists_returns200WithDefaultLayout() th

mockMvc.perform(get(BASE_URL).header("username", "new user"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username", nullValue()))
// Expecting application layout as defined in /test/resources/defaultApplicationLayout.json
.andExpect(jsonPath("$.definition.defaultLayoutKey", is("default-layout-value")));
.andExpect(jsonPath("$.defaultLayoutKey", is("default-layout-value")));
}

@Test
Expand Down Expand Up @@ -85,8 +88,7 @@ public void getApplicationLayout_layoutExists_returns200WithPersistedLayout() th

mockMvc.perform(get(BASE_URL).header("username", user))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username", is(user)))
.andExpect(jsonPath("$.definition", is(definition)));
.andExpect(jsonPath("$", is(definition)));
}

@Test
Expand All @@ -103,7 +105,7 @@ public void persistApplicationLayout_noLayoutExists_returns201AndPersistsLayout(
ApplicationLayout persistedLayout = repository.findById(user).orElseThrow();

assertThat(persistedLayout.getUsername()).isEqualTo(user);
assertThat(persistedLayout.getDefinition()).isEqualTo(objectMapper.readTree(definition));
assertThat(persistedLayout.getApplicationLayout()).isEqualTo(objectMapper.readTree(definition));
}

@Test
Expand All @@ -128,7 +130,7 @@ public void persistApplicationLayout_layoutExists_returns201AndOverwritesLayout(
ApplicationLayout retrievedLayout = repository.findById(user).orElseThrow();

assertThat(retrievedLayout.getUsername()).isEqualTo(user);
assertThat(retrievedLayout.getDefinition()).isEqualTo(objectMapper.readTree(newDefinition));
assertThat(retrievedLayout.getApplicationLayout()).isEqualTo(objectMapper.readTree(newDefinition));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.finos.vuu.layoutserver.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.finos.vuu.layoutserver.model.ApplicationLayout;
import org.finos.vuu.layoutserver.repository.ApplicationLayoutRepository;
Expand Down Expand Up @@ -36,20 +35,21 @@ public void setup() {
}

@Test
public void getApplicationLayout_noLayout_returnsDefault() throws JsonProcessingException {
public void getApplicationLayout_noLayout_returnsDefault() {
when(mockRepo.findById(anyString())).thenReturn(Optional.empty());

ApplicationLayout actualLayout = service.getApplicationLayout("new user");

// Expecting application layout as defined in /test/resources/defaultApplicationLayout.json
ObjectNode expectedDefinition = objectNodeConverter.convertToEntityAttribute("{\"defaultLayoutKey\":\"default-layout-value\"}");
ObjectNode expectedDefinition =
objectNodeConverter.convertToEntityAttribute("{\"defaultLayoutKey\":\"default-layout-value\"}");

assertThat(actualLayout.getUsername()).isNull();
assertThat(actualLayout.getDefinition()).isEqualTo(expectedDefinition);
assertThat(actualLayout.getApplicationLayout()).isEqualTo(expectedDefinition);
}

@Test
public void getApplicationLayout_layoutExists_returnsLayout() throws JsonProcessingException {
public void getApplicationLayout_layoutExists_returnsLayout() {
String user = "user";

ObjectNode expectedDefinition = objectNodeConverter.convertToEntityAttribute("{\"id\":\"main-tabs\"}");
Expand All @@ -63,7 +63,7 @@ public void getApplicationLayout_layoutExists_returnsLayout() throws JsonProcess
}

@Test
public void createApplicationLayout_validDefinition_callsRepoSave() throws JsonProcessingException {
public void createApplicationLayout_validDefinition_callsRepoSave() {
String user = "user";
ObjectNode definition = objectNodeConverter.convertToEntityAttribute("{\"id\":\"main-tabs\"}");

Expand Down
Loading
Loading