Repository: getmoto/moto

Issue:
Impossible to test Redshift cluster_restored waiters
Let us suppose we have this code:

```
# snap_restorer.py
def restore_snapshot(redshift_client, snapshot_id):
    cluster_identifier = 'temp_' + snapshot_id
    redshift_client.restore_from_cluster_snapshot(
        ClusterIdentifier=cluster_identifier,
        SnapshotIdentifier=snapshot_id,
    )
    redshift_client.get_waiter('cluster_restored').wait(
        ClusterIdentifier=cluster_identifier,
        WaiterConfig={
            'Delay': 1,
            'MaxAttempts': 2,
        }
    )
```

And we wish to test it like so:

```
# test_snap_restorer.py
import boto3
from moto import mock_redshift

from snap_restorer import restore_snapshot

@mock_redshift
def test_restore_snapshot():
    client = boto3.client('redshift', region_name='us-east-1')
    # Make a cluster for a snapshot to associate with
    client.create_cluster(
        ClusterIdentifier='whatever',
        MasterUsername='whoever',
        MasterUserPassword='wh473v3r',
        NodeType='multi-node',
        NumberOfNodes=2,
    )
    # And a snapshot to test with
    client.create_cluster_snapshot(
        SnapshotIdentifier='testsnapshot',
        ClusterIdentifier='whatever',
    )

    restore_snapshot(client, 'testsnapshot')
```

This test will promptly fail thanks to the tight settings for the `WaiterConfig`, but if left at its defaults the test would instead hang for thirty minutes, and then fail.

Moto's Redshift backend will never generate the `RestoreStatus` object that botocore's `ClusterRestored` waiter searches for, so we can't mock out this kind of interaction yet.


Discussion:
Yeah, we've struggled in the past with these types of timing-based tests. 

Any thoughts on how you would want it to work? Some API in Moto that allows you to manipulate time forward?
I don't even care so much about simulating time inside moto, at least not right now; I just want to be able to test interactions that happen to include waiters at all.

In this particular case, creating the new mocked cluster with a `RestoreStatus` of `{'Status': 'completed'}` immediately (when created from the `restore_from_cluster_snapshot` method, of course) would satisfy the waiter and allow the test to pass.

Resolving patch:
diff --git a/moto/redshift/models.py b/moto/redshift/models.py
--- a/moto/redshift/models.py
+++ b/moto/redshift/models.py
@@ -73,7 +73,8 @@ def __init__(self, redshift_backend, cluster_identifier, node_type, master_usern
                  preferred_maintenance_window, cluster_parameter_group_name,
                  automated_snapshot_retention_period, port, cluster_version,
                  allow_version_upgrade, number_of_nodes, publicly_accessible,
-                 encrypted, region_name, tags=None, iam_roles_arn=None):
+                 encrypted, region_name, tags=None, iam_roles_arn=None,
+                 restored_from_snapshot=False):
         super(Cluster, self).__init__(region_name, tags)
         self.redshift_backend = redshift_backend
         self.cluster_identifier = cluster_identifier
@@ -119,6 +120,7 @@ def __init__(self, redshift_backend, cluster_identifier, node_type, master_usern
             self.number_of_nodes = 1
 
         self.iam_roles_arn = iam_roles_arn or []
+        self.restored_from_snapshot = restored_from_snapshot
 
     @classmethod
     def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
@@ -242,7 +244,15 @@ def to_json(self):
                 "IamRoleArn": iam_role_arn
             } for iam_role_arn in self.iam_roles_arn]
         }
-
+        if self.restored_from_snapshot:
+            json_response['RestoreStatus'] = {
+                'Status': 'completed',
+                'CurrentRestoreRateInMegaBytesPerSecond': 123.0,
+                'SnapshotSizeInMegaBytes': 123,
+                'ProgressInMegaBytes': 123,
+                'ElapsedTimeInSeconds': 123,
+                'EstimatedTimeToCompletionInSeconds': 123
+            }
         try:
             json_response['ClusterSnapshotCopyStatus'] = self.cluster_snapshot_copy_status
         except AttributeError:
@@ -639,7 +649,8 @@ def restore_from_cluster_snapshot(self, **kwargs):
             "cluster_version": snapshot.cluster.cluster_version,
             "number_of_nodes": snapshot.cluster.number_of_nodes,
             "encrypted": snapshot.cluster.encrypted,
-            "tags": snapshot.cluster.tags
+            "tags": snapshot.cluster.tags,
+            "restored_from_snapshot": True
         }
         create_kwargs.update(kwargs)
         return self.create_cluster(**create_kwargs)


Test patch:
diff --git a/tests/test_redshift/test_redshift.py b/tests/test_redshift/test_redshift.py
--- a/tests/test_redshift/test_redshift.py
+++ b/tests/test_redshift/test_redshift.py
@@ -818,6 +818,48 @@ def test_create_cluster_from_snapshot():
     new_cluster['Endpoint']['Port'].should.equal(1234)
 
 
+@mock_redshift
+def test_create_cluster_from_snapshot_with_waiter():
+    client = boto3.client('redshift', region_name='us-east-1')
+    original_cluster_identifier = 'original-cluster'
+    original_snapshot_identifier = 'original-snapshot'
+    new_cluster_identifier = 'new-cluster'
+
+    client.create_cluster(
+        ClusterIdentifier=original_cluster_identifier,
+        ClusterType='single-node',
+        NodeType='ds2.xlarge',
+        MasterUsername='username',
+        MasterUserPassword='password',
+    )
+    client.create_cluster_snapshot(
+        SnapshotIdentifier=original_snapshot_identifier,
+        ClusterIdentifier=original_cluster_identifier
+    )
+    response = client.restore_from_cluster_snapshot(
+        ClusterIdentifier=new_cluster_identifier,
+        SnapshotIdentifier=original_snapshot_identifier,
+        Port=1234
+    )
+    response['Cluster']['ClusterStatus'].should.equal('creating')
+
+    client.get_waiter('cluster_restored').wait(
+        ClusterIdentifier=new_cluster_identifier,
+        WaiterConfig={
+            'Delay': 1,
+            'MaxAttempts': 2,
+        }
+    )
+
+    response = client.describe_clusters(
+        ClusterIdentifier=new_cluster_identifier
+    )
+    new_cluster = response['Clusters'][0]
+    new_cluster['NodeType'].should.equal('ds2.xlarge')
+    new_cluster['MasterUsername'].should.equal('username')
+    new_cluster['Endpoint']['Port'].should.equal(1234)
+
+
 @mock_redshift
 def test_create_cluster_from_non_existent_snapshot():
     client = boto3.client('redshift', region_name='us-east-1')