Repository: hedyorg/hedy

Issue:
Deletion of account with programs throws a 500
When fixed, also add E2E tests to cover this scenario.

Stack trace:

```
  File "/home/hq/Desktop/hedy/website/auth.py", line 74, in inner
    return f (user, *args, **kws)
  File "/home/hq/Desktop/hedy/website/auth.py", line 278, in destroy
    DATABASE.forget_user(user ['username'])
  File "/home/hq/Desktop/hedy/website/database.py", line 91, in forget_user
    PROGRAMS.del_many({'username': username})
  File "/home/hq/Desktop/hedy/website/querylog.py", line 176, in wrapped
    return fn(*args, **kwargs)
  File "/home/hq/Desktop/hedy/website/dynamo.py", line 117, in del_many
    to_delete = self.get_many(key)
  File "/home/hq/Desktop/hedy/website/querylog.py", line 176, in wrapped
    return fn(*args, **kwargs)
  File "/home/hq/Desktop/hedy/website/dynamo.py", line 64, in get_many
    items = self.storage.query(lookup.table_name, key, reverse=reverse)
  File "/home/hq/Desktop/hedy/website/dynamo.py", line 175, in query
    result = self.db.query(
  File "/home/hq/Desktop/hedy/.env/lib/python3.8/site-packages/botocore/client.py", line 357, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/home/hq/Desktop/hedy/.env/lib/python3.8/site-packages/botocore/client.py", line 648, in _make_api_call
    request_dict = self._convert_to_request_dict(
  File "/home/hq/Desktop/hedy/.env/lib/python3.8/site-packages/botocore/client.py", line 696, in _convert_to_request_dict
    request_dict = self._serializer.serialize_to_request(
  File "/home/hq/Desktop/hedy/.env/lib/python3.8/site-packages/botocore/validate.py", line 297, in serialize_to_request
    raise ParamValidationError(report=report.generate_report())
botocore.exceptions.ParamValidationError: Parameter validation failed:
Unknown parameter in input: "Key", must be one of: TableName, IndexName, Select, AttributesToGet, Limit, ConsistentRead, KeyConditions, QueryFilter, ConditionalOperator, ScanIndexForward, ExclusiveStartKey, ReturnConsumedCapacity, ProjectionExpression, FilterExpression, KeyConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues
```


Discussion:

Resolving patch:
diff --git a/website/dynamo.py b/website/dynamo.py
--- a/website/dynamo.py
+++ b/website/dynamo.py
@@ -172,9 +172,13 @@ def get_item(self, table_name, key):
         return self._decode(result.get('Item', None))
 
     def query(self, table_name, key, reverse=False):
+        assert len(key.keys ()) == 1
+        key_field = list (key.keys ()) [0]
+        key_value = key [key_field]
         result = self.db.query(
             TableName=self.db_prefix + '-' + table_name,
-            Key = self._encode(key),
+            KeyConditionExpression = key_field + ' = :value',
+            ExpressionAttributeValues = {':value': self.SERIALIZER.serialize (key_value)},
             ScanIndexForward = not reverse)
         return list(map(self._decode, result.get('Items', [])))
 
@@ -460,4 +464,4 @@ def decode_object(obj):
         """The decoding for the encoding above."""
         if obj.get('$type') == 'set':
             return set(obj['elements'])
-        return obj
\ No newline at end of file
+        return obj


Test patch:
diff --git a/e2e_tests.py b/e2e_tests.py
--- a/e2e_tests.py
+++ b/e2e_tests.py
@@ -449,6 +449,7 @@ def suite (username):
         ['unshare program', 'post', '/programs/share', {}, lambda state: {'id': state ['program'] ['id'], 'public': False}, 200],
         ['delete program', 'get', lambda state: '/programs/delete/' + state ['program'] ['id'], {}, {}, 302],
         ['retrieve programs after deleting saved program', 'get', '/programs_list', {}, {}, 200, retrieveProgramsBefore],
+        ['create program before destroying account', 'post', '/programs', {}, {'code': 'print Hello world', 'name': 'Program 1', 'level': 1}, 200],
         ['destroy account', 'post', '/auth/destroy', {}, {}, 200],
         ['get programs without being logged in', 'get', '/programs', {}, {}, 302],
         # Auth: profile (programming experience)