Skip to content

Commit

Permalink
GraphQL entity restore endpoint. Functional test added jmix-projects/…
Browse files Browse the repository at this point in the history
  • Loading branch information
dtaimanov committed Dec 3, 2021
1 parent c52eb62 commit 5a6a06b
Show file tree
Hide file tree
Showing 9 changed files with 570 additions and 0 deletions.
56 changes: 56 additions & 0 deletions jmix-datatools-graphql/jmix-datatools-graphql.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,62 @@
archivesBaseName = 'jmix-datatools-graphql'

apply plugin: 'groovy'

dependencies {
compileOnly 'io.jmix.datatools:jmix-datatools'
compileOnly 'io.jmix.graphql:jmix-graphql'
testImplementation 'io.jmix.graphql:jmix-graphql-starter'


testImplementation 'org.spockframework:spock-core'
testImplementation 'org.spockframework:spock-spring'
testImplementation('org.springframework.boot:spring-boot-starter-test')
{
exclude module: "spring-boot-starter-logging"
}

testImplementation('com.graphql-java-kickstart:graphql-spring-boot-starter-test:11.0.0')
{
// todo configure logger correctly
exclude module: "spring-boot-starter-logging"
}
testImplementation 'io.jmix.core:jmix-core-starter'
testImplementation 'io.jmix.data:jmix-data'
testImplementation 'io.jmix.data:jmix-eclipselink-starter'
testImplementation 'io.jmix.security:jmix-security-starter'
testImplementation 'io.jmix.security:jmix-security-data-starter'
testImplementation 'io.jmix.security:jmix-security-oauth2-starter'

testImplementation 'com.google.code.gson:gson:2.8.6'

testImplementation 'org.springframework:spring-test'
testImplementation 'org.springframework.security:spring-security-test'

testImplementation 'org.apache.commons:commons-dbcp2'

testImplementation 'org.apache.httpcomponents.client5:httpclient5:5.0.3'
testImplementation 'org.apache.httpcomponents.core5:httpcore5-reactive:5.0.2'

testImplementation 'io.rest-assured:rest-assured'

testRuntimeOnly 'org.slf4j:slf4j-simple'
testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
testRuntimeOnly 'org.junit.vintage:junit-vintage-engine'


testImplementation 'io.jmix.datatools:jmix-datatools-starter'

testRuntimeOnly 'org.slf4j:slf4j-simple'
testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
testRuntimeOnly 'org.junit.vintage:junit-vintage-engine'

}

test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2021 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.jmix.datatools.graphql

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.graphql.spring.boot.test.GraphQLResponse
import com.graphql.spring.boot.test.GraphQLTestTemplate
import io.jmix.core.security.InMemoryUserRepository
import io.jmix.security.authentication.RoleGrantedAuthority
import io.jmix.security.role.ResourceRoleRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.web.server.LocalServerPort
import org.springframework.http.HttpHeaders
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.TransactionDefinition
import org.springframework.transaction.support.TransactionTemplate
import spock.lang.Specification
import test_support.App
import test_support.RestTestUtils

@SpringBootTest(classes = App, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class AbstractGraphQLTest extends Specification {

@LocalServerPort
int port
@Autowired
GraphQLTestTemplate graphQLTestTemplate
@Autowired
ResourceRoleRepository resourceRoleRepository
@Autowired
InMemoryUserRepository userRepository

protected TransactionTemplate transaction
protected String adminToken
protected UserDetails admin

void setup() {
admin = User.builder()
.username("admin")
.password("{noop}admin")
.authorities(RoleGrantedAuthority.ofResourceRole(resourceRoleRepository.getRoleByCode("system-full-access")))
.build()
userRepository.addUser(admin)

adminToken = RestTestUtils.getAuthToken("admin", "admin", port)
}

@Autowired
protected void setTransactionManager(PlatformTransactionManager transactionManager) {
transaction = new TransactionTemplate(transactionManager)
transaction.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW)
}

protected GraphQLResponse query(String queryFilePath) {
return graphQLTestTemplate
.withBearerAuth(adminToken)
.postForResource("io/jmix/datatools/graphql/" + queryFilePath)
}

protected GraphQLResponse query(String queryFilePath, HttpHeaders httpHeaders) {
return graphQLTestTemplate
.withHeaders(httpHeaders)
.withBearerAuth(adminToken)
.postForResource("io/jmix/datatools/graphql/" + queryFilePath)
}

protected GraphQLResponse query(String queryFilePath, ObjectNode variables) {
return query(queryFilePath, variables, adminToken)
}

protected GraphQLResponse query(String queryFilePath, String variables) {
return query(queryFilePath, asObjectNode(variables), adminToken)
}

protected GraphQLResponse query(String queryFilePath, ObjectNode variables, String token) {
return graphQLTestTemplate
.withBearerAuth(token)
.perform("graphql/io/jmix/graphql/" + queryFilePath, variables)
}

static ObjectNode asObjectNode(String str) {
return new ObjectMapper().readValue(str, ObjectNode.class)
}

static String getBody(GraphQLResponse response) {
return response.rawResponse.body
}

static JsonObject getExtensions(JsonObject error) {
error.getAsJsonObject("extensions").getAsJsonObject()
}

static String getMessage(JsonObject jsonObject) {
jsonObject.get("message").getAsString()
}

static String getPath(JsonObject jsonObject) {
jsonObject.get("path").getAsString()
}

static JsonArray getErrors(GraphQLResponse response) {
JsonParser.parseString(response.rawResponse.body).getAsJsonArray("errors")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.jmix.datatools.graphql

import com.graphql.spring.boot.test.GraphQLResponse
import io.jmix.core.UnconstrainedDataManager
import org.springframework.beans.factory.annotation.Autowired
import test_support.entity.Customer

class DatatoolsGraphqlTest extends AbstractGraphQLTest {
private static final UUID ID = UUID.fromString("bb7b5923-5b88-4dd7-aad9-9d2f735009dd")

@Autowired
private UnconstrainedDataManager dataManager

def "Entity restore endpoint works"() {
when:
Customer customer = dataManager.create(Customer)
customer.id = ID
customer.name = "Ivan"
customer = dataManager.save(customer)
dataManager.remove(customer)

GraphQLResponse response = query("test/restoreEntities.graphql")

Optional<Customer> loaded = dataManager.load(Customer).id(customer.id).optional()

then:
response.getRawResponse().getBody() == '{"data":{"restoreEntities":1}}'
loaded.isPresent()

}

}


81 changes: 81 additions & 0 deletions jmix-datatools-graphql/src/test/java/test_support/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2021 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package test_support;

import io.jmix.core.JmixModules;
import io.jmix.core.Resources;
import io.jmix.core.Stores;
import io.jmix.core.security.InMemoryUserRepository;
import io.jmix.core.security.UserRepository;
import io.jmix.data.impl.JmixEntityManagerFactoryBean;
import io.jmix.data.persistence.DbmsSpecifics;
import io.jmix.security.StandardSecurityConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import javax.sql.DataSource;


@SpringBootApplication(scanBasePackages = "io.jmix.datatools.graphql")
@PropertySource("classpath:/test_support/test-app.properties")
public class App {

public static void main(String[] args) {
SpringApplication.run(App.class, args);
}

@Bean
@Primary
@LiquibaseDataSource
DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.HSQL)
.build();
}

@Bean
@Primary
UserRepository userRepository() {
return new InMemoryUserRepository();
}


@Bean
@Primary
LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
DbmsSpecifics dbmsSpecifics,
JmixModules jmixModules,
JpaVendorAdapter jpaVendorAdapter,
Resources resources) {
return new JmixEntityManagerFactoryBean(Stores.MAIN, dataSource, jpaVendorAdapter, dbmsSpecifics, jmixModules, resources);
}


@EnableWebSecurity
static class TestSecurityConfiguration extends StandardSecurityConfiguration {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2019 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package test_support;

import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.ReadContext;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.*;

import static org.apache.http.HttpStatus.SC_OK;

/**
* Utility class for the REST API functional tests
*/
public class RestTestUtils {

public static final String CLIENT_ID = "client";
public static final String CLIENT_SECRET = "secret";

public static String getAuthToken(String login, String password, int port) throws IOException {
return getAuthToken("http://localhost:" + port, login, password, new HashMap<>());
}

public static String getAuthToken(String baseUrl, String login, String password, Map<String, String> headers) throws IOException {
String uri = baseUrl + "/oauth/token";

CloseableHttpClient httpClient = HttpClients.createDefault();
String encoding = Base64.getEncoder().encodeToString((CLIENT_ID + ":" + CLIENT_SECRET).getBytes());
HttpPost httpPost = new HttpPost(uri);
httpPost.setHeader("Authorization", "Basic " + encoding);
for (Map.Entry<String, String> entry : headers.entrySet()) {
httpPost.setHeader(entry.getKey(), entry.getValue());
}

List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
urlParameters.add(new BasicNameValuePair("grant_type", "password"));
urlParameters.add(new BasicNameValuePair("username", login));
urlParameters.add(new BasicNameValuePair("password", password));

httpPost.setEntity(new UrlEncodedFormEntity(urlParameters));

try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != SC_OK) {
throw new AuthException(statusCode);
}
ReadContext ctx = parseResponse(response);
return ctx.read("$.access_token");
}
}

public static ReadContext parseResponse(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
String s = EntityUtils.toString(entity);
return JsonPath.parse(s);
}


public static class AuthException extends RuntimeException {

private int statusCode;

public AuthException(int statusCode) {
this.statusCode = statusCode;
}
}
}

0 comments on commit 5a6a06b

Please sign in to comment.