/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.usergrid.rest.applications; import com.fasterxml.jackson.databind.JsonNode; import org.apache.usergrid.rest.test.resource.AbstractRestIT; import org.apache.usergrid.rest.test.resource.endpoints.mgmt.ManagementResponse; import org.apache.usergrid.rest.test.resource.model.ApiResponse; import org.apache.usergrid.rest.test.resource.model.Application; import org.apache.usergrid.rest.test.resource.model.Entity; import org.apache.usergrid.rest.test.resource.model.Token; import org.junit.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.ClientErrorException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.UUID; import static org.junit.Assert.fail; import static org.apache.usergrid.rest.management.organizations.applications .ApplicationResource.CONFIRM_APPLICATION_IDENTIFIER; public class ApplicationDeleteIT extends AbstractRestIT { private static final Logger logger = LoggerFactory.getLogger(ApplicationDeleteIT.class); public static final int INDEXING_WAIT = 3000; @Test public void testBasicOperation() throws Exception { // create app with a collection of "things" String orgName = clientSetup.getOrganization().getName(); String appToDeleteName = clientSetup.getAppName() + "_appToDelete"; Token orgAdminToken = getAdminToken( clientSetup.getUsername(), clientSetup.getUsername()); List<Entity> entities = new ArrayList<>(); UUID appToDeleteId = createAppWithCollection(orgName, appToDeleteName, orgAdminToken, entities); // delete the app clientSetup.getRestClient().management().orgs() .org(orgName).apps().app(appToDeleteId.toString() ).getTarget() .queryParam("access_token", orgAdminToken.getAccessToken() ) .queryParam(CONFIRM_APPLICATION_IDENTIFIER, appToDeleteId) .request().delete(); // test that we can create a new application with the same name ApiResponse appCreateAgainResponse = null; int retries = 0; while ( retries++ < 30 ) { try { appCreateAgainResponse = clientSetup.getRestClient() .management().orgs().org( orgName ).app().getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ).request() .post( javax.ws.rs.client.Entity.json( new Application( appToDeleteName ) ), ApiResponse.class ); break; } catch (Exception e) { logger.error("App not deleted yet. Waiting ... ({})", retries); Thread.sleep( 1000 ); } } Assert.assertNotNull( appCreateAgainResponse ); Assert.assertEquals("Must be able to create app with same name as deleted app", (orgName + "/" + appToDeleteName).toLowerCase(), appCreateAgainResponse.getEntities().get(0).get( "name" )); } /** * Test most common use cases. * <pre> * - create app with collection of things * - delete the app * - test that attempts to get the app, its collections and entities throw 400 with message * - test that we cannot delete the app a second time * - test that we can create a new app with the same name as the deleted app * </pre> */ @Test public void testCommonUseCases() throws Exception { // create app with a collection of "things" String orgName = clientSetup.getOrganization().getName(); String appToDeleteName = clientSetup.getAppName() + "_appToDelete"; Token orgAdminToken = getAdminToken( clientSetup.getUsername(), clientSetup.getUsername()); List<Entity> entities = new ArrayList<>(); UUID appToDeleteId = createAppWithCollection(orgName, appToDeleteName, orgAdminToken, entities); // delete the app without specifying confirm_application_identifier final Response response = clientSetup.getRestClient().management().orgs() .org( orgName ).apps().app( appToDeleteId.toString() ).getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ) .request() .delete(); Assert.assertEquals("Error must be 400", 400, response.getStatus() ); clientSetup.getRestClient().management().orgs() .org(orgName).apps().app(appToDeleteId.toString() ).getTarget() .queryParam("access_token", orgAdminToken.getAccessToken() ) .queryParam(CONFIRM_APPLICATION_IDENTIFIER, appToDeleteId) .request() .delete(); // test that we can no longer get the app try { // using /management/orgs/{org-name}/app/{app-name} clientSetup.getRestClient().management().orgs() .org(orgName).apps().app(appToDeleteName).getTarget() .queryParam("access_token", orgAdminToken.getAccessToken()) .request() .get(ApiResponse.class); fail("Must not be able to get deleted app"); } catch ( ClientErrorException expected ) { Assert.assertEquals("Error must be 404", 404, expected.getResponse().getStatus() ); JsonNode node = mapper.readTree( expected.getResponse().readEntity( String.class )); Assert.assertEquals("entity_not_found", node.get("error").textValue()); } try { // using /{org-name}/{app-name} path clientSetup.getRestClient().org( orgName ).app( appToDeleteName ).getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ).request() .get( ApiResponse.class ); fail( "Must not be able to get deleted app" ); } catch ( ClientErrorException expected ) { Assert.assertEquals( "Error must be 404", 404, expected.getResponse().getStatus() ); JsonNode node = mapper.readTree( expected.getResponse().readEntity( String.class ) ); Assert.assertEquals( "organization_application_not_found", node.get( "error" ).textValue() ); } // test that we can no longer get deleted app's collection try { clientSetup.getRestClient() .org(orgName).app(appToDeleteName).collection("things").getTarget() .queryParam("access_token", orgAdminToken.getAccessToken() ) .request() .get( ApiResponse.class ); fail("Must not be able to get deleted app's collection"); } catch ( ClientErrorException expected ) { Assert.assertEquals("Error must be 400", 404, expected.getResponse().getStatus() ); JsonNode node = mapper.readTree( expected.getResponse().readEntity( String.class )); Assert.assertEquals("organization_application_not_found", node.get("error").textValue()); } // test that we can no longer get an app entity try { UUID entityId = entities.get(0).getUuid(); clientSetup.getRestClient() .org(orgName).app(appToDeleteName).collection("things").entity( entityId ).getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken()) .request() .get( ApiResponse.class ); fail("Must not be able to get deleted app entity"); } catch ( ClientErrorException expected ) { // TODO: why not a 404? Assert.assertEquals("Error must be 400", 404, expected.getResponse().getStatus() ); JsonNode node = mapper.readTree( expected.getResponse().readEntity( String.class )); Assert.assertEquals("organization_application_not_found", node.get("error").textValue()); } // test that we cannot see the application in the list of applications returned // by the management resource's get organization's applications end-point waitForQueueDrainAndRefreshIndex(); ManagementResponse orgAppResponse = clientSetup.getRestClient() .management().orgs().org( orgName ).apps().getOrganizationApplications(); for ( String appName : orgAppResponse.getData().keySet() ) { if ( orgAppResponse.getData().get( appName ).equals( appToDeleteId.toString() )) { fail("Deleted app must not be included in list of org apps"); } } // test that we cannot delete the application a second time final Response response1 = clientSetup.getRestClient().management() .orgs().org( orgName ).apps().app( appToDeleteId.toString() ) .getTarget().queryParam( "access_token", orgAdminToken.getAccessToken() ) .queryParam( CONFIRM_APPLICATION_IDENTIFIER, appToDeleteId ) .request() .delete(); Assert.assertEquals( "Error must be 404", 404, response1.getStatus() ); // test that we can create a new application with the same name ApiResponse appCreateAgainResponse = null; int retries = 0; while ( retries++ < 20 ) { try { appCreateAgainResponse = clientSetup.getRestClient() .management().orgs().org( orgName ).app().getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ).request() .post( javax.ws.rs.client.Entity.json( new Application( appToDeleteName ) ), ApiResponse.class ); break; } catch (Exception e) { logger.error("App not deleted yet. Waiting ... ({})", retries); Thread.sleep( 1000 ); } } Assert.assertNotNull( appCreateAgainResponse ); Assert.assertEquals("Must be able to create app with same name as deleted app", (orgName + "/" + appToDeleteName).toLowerCase(), appCreateAgainResponse.getEntities().get(0).get( "name" )); } /** * Test restore of deleted app. * <pre> * - create app with collection of things * - delete the app * - restore the app * - test that we can get the app, its collections and an entity * </pre> */ @Test public void testAppRestore() throws Exception { // create app with a collection of "things" String orgName = clientSetup.getOrganization().getName(); String appToDeleteName = clientSetup.getAppName() + "_appToDelete"; Token orgAdminToken = getAdminToken( clientSetup.getUsername(), clientSetup.getUsername()); List<Entity> entities = new ArrayList<>(); UUID appToDeleteId = createAppWithCollection(orgName, appToDeleteName, orgAdminToken, entities); // delete the app logger.debug( "\n\nDeleting app\n" ); clientSetup.getRestClient().management().orgs() .org( orgName ).apps().app( appToDeleteName ).getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ) .queryParam(CONFIRM_APPLICATION_IDENTIFIER, appToDeleteName) .request() .delete(); waitForQueueDrainAndRefreshIndex(); // restore the app logger.debug( "\n\nRestoring app\n" ); clientSetup.getRestClient().management().orgs() .org( orgName ).apps().app( appToDeleteId.toString() ).getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ) .request() .put( javax.ws.rs.client.Entity.entity( "", MediaType.APPLICATION_JSON )); // must send body waitForQueueDrainAndRefreshIndex(); // test that we can see the application in the list of applications logger.debug("\n\nGetting app list from management end-point\n"); ManagementResponse orgAppResponse = clientSetup.getRestClient() .management().orgs().org( orgName ).apps().getOrganizationApplications(); boolean found = false; for ( String appName : orgAppResponse.getData().keySet() ) { if ( orgAppResponse.getData().get( appName ).equals( appToDeleteId.toString() )) { found = true; break; } } Assert.assertTrue( found ); // test that we can get an app entity logger.debug( "\n\nGetting entities from app\n" ); UUID entityId = entities.get( 0 ).getUuid(); ApiResponse entityResponse = clientSetup.getRestClient() .org( orgName ).app( appToDeleteName ).collection( "things" ).entity( entityId ).getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ) .request() .get( ApiResponse.class ); Assert.assertEquals( entityId, entityResponse.getEntities().get(0).getUuid() ); // test that we can get deleted app's collection ApiResponse collectionReponse = clientSetup.getRestClient() .org( orgName ).app( appToDeleteName ).collection( "things" ).getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ) .request() .get( ApiResponse.class ); Assert.assertEquals( entities.size(), collectionReponse.getEntityCount() ); } /** * Test that we cannot restore deleted app with same name as */ @Test public void testAppRestoreConflict() throws Exception { // create app with a collection of "things" String orgName = clientSetup.getOrganization().getName(); String appToDeleteName = clientSetup.getAppName() + "_appToDelete"; Token orgAdminToken = getAdminToken( clientSetup.getUsername(), clientSetup.getUsername()); List<Entity> entities = new ArrayList<>(); UUID appToDeleteId = createAppWithCollection(orgName, appToDeleteName, orgAdminToken, entities); // delete the app clientSetup.getRestClient().management().orgs() .org( orgName ).apps().app( appToDeleteId.toString() ).getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ) .queryParam(CONFIRM_APPLICATION_IDENTIFIER, appToDeleteId) .request() .delete(); // create new app with same name UUID newAppId = null; int retries = 0; while ( retries++ < 20 ) { try { newAppId = createAppWithCollection( orgName, appToDeleteName, orgAdminToken, entities ); break; } catch ( Exception e ) { logger.error("Application not deleted yet. Waiting... ({})", retries); Thread.sleep(1000); } } Assert.assertNotNull( newAppId ); // attempt to restore original app, should get 409 final Response response = clientSetup.getRestClient().management().orgs() .org( orgName ).apps().app( appToDeleteId.toString() ).getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ) .request() .put( javax.ws.rs.client.Entity.entity( "", MediaType.TEXT_PLAIN ) );// must send body with put Assert.assertEquals( 409, response.getStatus() ); } /** * Test that we cannot delete an app with same name as an app that is already deleted. * TODO: investigate way to support this, there should be no such restriction. */ @Test public void testAppDeleteConflict() throws Exception { // create app with a collection of "things" String orgName = clientSetup.getOrganization().getName(); String appToDeleteName = clientSetup.getAppName() + "_appToDelete"; Token orgAdminToken = getAdminToken( clientSetup.getUsername(), clientSetup.getUsername()); List<Entity> entities = new ArrayList<>(); UUID appToDeleteId = createAppWithCollection(orgName, appToDeleteName, orgAdminToken, entities); // delete the app clientSetup.getRestClient().management() .orgs().org( orgName ).apps().app( appToDeleteId.toString() ).getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ) .queryParam( CONFIRM_APPLICATION_IDENTIFIER, appToDeleteId ) .request().delete(); // create new app with same name UUID newAppId = null; int retries = 0; while ( retries++ < 20 ) { try { newAppId = createAppWithCollection( orgName, appToDeleteName, orgAdminToken, entities ); break; } catch ( Exception e ) { logger.error("Application not deleted yet. Waiting... ({})", retries); Thread.sleep(1000); } } Assert.assertNotNull( newAppId ); // attempt to delete new app, it should fail final Response response = clientSetup.getRestClient().management() .orgs().org( orgName ).apps().app( newAppId.toString() ).getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ) .queryParam( CONFIRM_APPLICATION_IDENTIFIER, newAppId ) .request() .delete(); Assert.assertEquals( 409, response.getStatus() ); } private UUID createAppWithCollection( String orgName, String appName, Token orgAdminToken, List<Entity> entities) { ApiResponse appCreateResponse = clientSetup.getRestClient() .management().orgs().org( orgName ).app().getTarget() .queryParam( "access_token", orgAdminToken.getAccessToken() ) .request() .post( javax.ws.rs.client.Entity.json(new Application( appName )), ApiResponse.class ); UUID appId = appCreateResponse.getEntities().get(0).getUuid(); try { Thread.sleep(INDEXING_WAIT); } catch (InterruptedException ignored ) { } for ( int i=0; i<10; i++ ) { final String entityName = "entity" + i; Entity entity = new Entity(); entity.setProperties( new HashMap<String, Object>() {{ put( "name", entityName ); }} ); ApiResponse createResponse = clientSetup.getRestClient() .org(orgName).app( appName ).collection("things").getTarget() .queryParam("access_token", orgAdminToken.getAccessToken()) .request() .post( javax.ws.rs.client.Entity.json(entity), ApiResponse.class ); entities.add( createResponse.getEntities().get(0) ); } return appId; } }