Repository: spring-cloud/spring-cloud-dataflow
Issue: REST: Retrieving non-existing app details should return a 404

The issue is in class `AppRegistryController#DetailedAppRegistrationResource`

When executing a **GET** `/apps/{type}/{name}` a `200` HTTP Status is returned.

But it should produce a `404`.

We need to ensure that when making this minor **breaking change** that the shell and web UI are working.

Changed files:
spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AppRegistryController.java
spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/AppRegistryCommands.java
spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/controller/AppRegistryControllerTests.java

diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AppRegistryController.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AppRegistryController.java
index 092e4e259..51dd91e31 100644
--- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AppRegistryController.java
+++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AppRegistryController.java
@@ -109,7 +109,7 @@ public class AppRegistryController {
 			@PathVariable("name") String name) {
 		AppRegistration registration = appRegistry.find(name, type);
 		if (registration == null) {
-			return null;
+			throw new NoSuchAppRegistrationException(name, type);
 		}
 		DetailedAppRegistrationResource result = new DetailedAppRegistrationResource(assembler.toResource(registration));
 		Resource resource = registration.getResource();
diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/controller/AppRegistryControllerTests.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/controller/AppRegistryControllerTests.java
index 011c55384..25add9adf 100644
--- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/controller/AppRegistryControllerTests.java
+++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/controller/AppRegistryControllerTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 the original author or authors.
+ * Copyright 2016-2017 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -124,6 +124,13 @@ public class AppRegistryControllerTests {
 		.andExpect(status().isOk()).andExpect(jsonPath("content", hasSize(4)));
 	}
 
+	@Test
+	public void testFindNonExistentApp() throws Exception {
+		mockMvc.perform(
+				get("/apps/source/foo").accept(MediaType.APPLICATION_JSON)).andDo(print())
+				.andExpect(status().is4xxClientError()).andReturn().getResponse().getContentAsString().contains("NoSuchAppRegistrationException");
+	}
+
 	@Test
 	public void testRegisterAndListApplications() throws Exception {
 		mockMvc.perform(
diff --git a/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/AppRegistryCommands.java b/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/AppRegistryCommands.java
index 2f7de5d9a..8a6b984e9 100644
--- a/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/AppRegistryCommands.java
+++ b/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/AppRegistryCommands.java
@@ -101,39 +101,44 @@ public class AppRegistryCommands implements CommandMarker, ResourceLoaderAware {
 					key = {"", "id"},
 					help = "id of the application to query in the form of 'type:name'")
 			QualifiedApplicationName application) {
-		DetailedAppRegistrationResource info = appRegistryOperations().info(application.name, application.type);
 		List<Object> result = new ArrayList<>();
-		if (info != null) {
-			List<ConfigurationMetadataProperty> options = info.getOptions();
-			result.add(String.format("Information about %s application '%s':", application.type, application.name));
-			result.add(String.format("Resource URI: %s", info.getUri()));
-			if (info.getShortDescription() != null) {
-				result.add(info.getShortDescription());
-			}
-			if (options == null) {
-				result.add("Application options metadata is not available");
-			}
-			else {
-				TableModelBuilder<Object> modelBuilder = new TableModelBuilder<>();
-				modelBuilder.addRow()
-						.addValue("Option Name")
-						.addValue("Description")
-						.addValue("Default")
-						.addValue("Type");
-				for (ConfigurationMetadataProperty option : options) {
+		try {
+			DetailedAppRegistrationResource info = appRegistryOperations().info(application.name, application.type);
+			if (info != null) {
+				List<ConfigurationMetadataProperty> options = info.getOptions();
+				result.add(String.format("Information about %s application '%s':", application.type, application.name));
+				result.add(String.format("Resource URI: %s", info.getUri()));
+				if (info.getShortDescription() != null) {
+					result.add(info.getShortDescription());
+				}
+				if (options == null) {
+					result.add("Application options metadata is not available");
+				}
+				else {
+					TableModelBuilder<Object> modelBuilder = new TableModelBuilder<>();
 					modelBuilder.addRow()
-							.addValue(option.getId())
-							.addValue(option.getDescription() == null ? "<unknown>" : option.getDescription())
-							.addValue(prettyPrintDefaultValue(option))
-							.addValue(option.getType() == null ? "<unknown>" : option.getType());
+							.addValue("Option Name")
+							.addValue("Description")
+							.addValue("Default")
+							.addValue("Type");
+					for (ConfigurationMetadataProperty option : options) {
+						modelBuilder.addRow()
+								.addValue(option.getId())
+								.addValue(option.getDescription() == null ? "<unknown>" : option.getDescription())
+								.addValue(prettyPrintDefaultValue(option))
+								.addValue(option.getType() == null ? "<unknown>" : option.getType());
+					}
+					TableBuilder builder = DataFlowTables.applyStyle(new TableBuilder(modelBuilder.build()))
+							.on(CellMatchers.table()).addSizer(new AbsoluteWidthSizeConstraints(30))
+							.and();
+					result.add(builder.build());
 				}
-				TableBuilder builder = DataFlowTables.applyStyle(new TableBuilder(modelBuilder.build()))
-						.on(CellMatchers.table()).addSizer(new AbsoluteWidthSizeConstraints(30))
-						.and();
-				result.add(builder.build());
+			}
+			else {
+				result.add(String.format("Application info is not available for %s:%s", application.type, application.name));
 			}
 		}
-		else {
+		catch (Exception e) {
 			result.add(String.format("Application info is not available for %s:%s", application.type, application.name));
 		}
 		return result;