Add basic stack-preview functional test

Change-Id: I401bc78488f62fe4d4674ae2d32678cce0242a56
Closes-bug: #1479594
diff --git a/functional/test_preview.py b/functional/test_preview.py
new file mode 100644
index 0000000..b4389ff
--- /dev/null
+++ b/functional/test_preview.py
@@ -0,0 +1,187 @@
+#    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.
+
+from heat_integrationtests.common import test
+from heatclient import exc
+import six
+
+
+class StackPreviewTest(test.HeatIntegrationTest):
+    template = '''
+heat_template_version: 2015-04-30
+parameters:
+  incomming:
+    type: string
+resources:
+  one:
+    type: OS::Heat::TestResource
+    properties:
+      value: fred
+  two:
+    type: OS::Heat::TestResource
+    properties:
+      value: {get_param: incomming}
+    depends_on: one
+outputs:
+  main_out:
+    value: {get_attr: [two, output]}
+    '''
+    env = '''
+parameters:
+  incomming: abc
+    '''
+
+    def setUp(self):
+        super(StackPreviewTest, self).setUp()
+        self.client = self.orchestration_client
+        self.project_id = self.identity_client.auth_ref.project_id
+
+    def _assert_resource(self, res, stack_name):
+        self.assertEqual(stack_name, res['stack_name'])
+        self.assertEqual('INIT', res['resource_action'])
+        self.assertEqual('COMPLETE', res['resource_status'])
+        for field in ('resource_status_reason', 'physical_resource_id',
+                      'description'):
+            self.assertIn(field, res)
+            self.assertEqual('', res[field])
+        for field in ('creation_time', 'updated_time'):
+            self.assertIn(field, res)
+            self.assertIsNotNone(res[field])
+        self.assertIn('output', res['attributes'])
+
+        # resource_identity
+        self.assertEqual(stack_name,
+                         res['resource_identity']['stack_name'])
+        self.assertEqual('None', res['resource_identity']['stack_id'])
+        self.assertEqual(self.project_id,
+                         res['resource_identity']['tenant'])
+        self.assertEqual('/resources/%s' % res['resource_name'],
+                         res['resource_identity']['path'])
+        # stack_identity
+        self.assertEqual(stack_name,
+                         res['stack_identity']['stack_name'])
+        self.assertEqual('None', res['stack_identity']['stack_id'])
+        self.assertEqual(self.project_id,
+                         res['stack_identity']['tenant'])
+        self.assertEqual('', res['stack_identity']['path'])
+
+    def _assert_results(self, result, stack_name):
+        # global stuff.
+        self.assertEqual(stack_name, result['stack_name'])
+        self.assertTrue(result['disable_rollback'])
+        self.assertEqual('None', result['id'])
+        self.assertIsNone(result['parent'])
+        self.assertEqual('No description', result['template_description'])
+
+        # parameters
+        self.assertEqual('None', result['parameters']['OS::stack_id'])
+        self.assertEqual(stack_name, result['parameters']['OS::stack_name'])
+        self.assertEqual('abc', result['parameters']['incomming'])
+
+    def test_basic_pass(self):
+        stack_name = self._stack_rand_name()
+        result = self.client.stacks.preview(
+            template=self.template,
+            stack_name=stack_name,
+            disable_rollback=True,
+            environment=self.env).to_dict()
+
+        self._assert_results(result, stack_name)
+        for res in result['resources']:
+            self._assert_resource(res, stack_name)
+            self.assertEqual('OS::Heat::TestResource',
+                             res['resource_type'])
+
+            # common properties
+            self.assertEqual(False, res['properties']['fail'])
+            self.assertEqual(0, res['properties']['wait_secs'])
+            self.assertEqual(False, res['properties']['update_replace'])
+
+            if res['resource_name'] == 'one':
+                self.assertEqual('fred', res['properties']['value'])
+                self.assertEqual(['two'], res['required_by'])
+            if res['resource_name'] == 'two':
+                self.assertEqual('abc', res['properties']['value'])
+                self.assertEqual([], res['required_by'])
+
+    def test_basic_fail(self):
+        stack_name = self._stack_rand_name()
+
+        # break the template so it fails validation.
+        wont_work = self.template.replace('get_param: incomming',
+                                          'get_param: missing')
+        excp = self.assertRaises(exc.HTTPBadRequest,
+                                 self.client.stacks.preview,
+                                 template=wont_work,
+                                 stack_name=stack_name,
+                                 disable_rollback=True,
+                                 environment=self.env)
+
+        self.assertIn('Property error: : resources.two.properties.value: '
+                      ': The Parameter (missing) was not provided.',
+                      six.text_type(excp))
+
+    def test_nested_pass(self):
+        """Nested stacks need to recurse down the stacks."""
+        main_template = '''
+heat_template_version: 2015-04-30
+parameters:
+  incomming:
+    type: string
+resources:
+  main:
+    type: nested.yaml
+    properties:
+      value: {get_param: incomming}
+outputs:
+  main_out:
+    value: {get_attr: [main, output]}
+    '''
+        nested_template = '''
+heat_template_version: 2015-04-30
+parameters:
+  value:
+    type: string
+resources:
+  nested:
+    type: OS::Heat::TestResource
+    properties:
+      value: {get_param: value}
+outputs:
+  output:
+    value: {get_attr: [nested, output]}
+'''
+        stack_name = self._stack_rand_name()
+        result = self.client.stacks.preview(
+            disable_rollback=True,
+            stack_name=stack_name,
+            template=main_template,
+            files={'nested.yaml': nested_template},
+            environment=self.env).to_dict()
+
+        self._assert_results(result, stack_name)
+
+        # nested resources return a list of their resources.
+        res = result['resources'][0][0]
+        nested_stack_name = '%s-%s' % (stack_name,
+                                       res['parent_resource'])
+
+        self._assert_resource(res, nested_stack_name)
+        self.assertEqual('OS::Heat::TestResource',
+                         res['resource_type'])
+
+        self.assertEqual(False, res['properties']['fail'])
+        self.assertEqual(0, res['properties']['wait_secs'])
+        self.assertEqual(False, res['properties']['update_replace'])
+
+        self.assertEqual('abc', res['properties']['value'])
+        self.assertEqual([], res['required_by'])