Ejemplo n.º 1
0
def test_suggest_participant_hidden_rig(mock_dynamodb, mock_participant_table,
                                        mock_wheel_table):
    participants = [{
        'id': get_uuid(),
        'wheel_id': WHEEL_ID,
        'name': name,
    } for name in ['Rig me!', 'I cannot win!']]

    with mock_participant_table.batch_writer() as batch:
        for participant in participants:
            batch.put_item(Item=participant)
    mock_wheel_table.update_item(Key={'id': WHEEL_ID},
                                 **to_update_kwargs({
                                     'rigging': {
                                         'hidden': True,
                                         'participant_id':
                                         participants[0]['id']
                                     }
                                 }))

    response = wheel_participant.suggest_participant({
        'body': {},
        'pathParameters': {
            'wheel_id': WHEEL_ID
        }
    })
    body = json.loads(response['body'])
    assert response['statusCode'] == 200
    assert body['participant_id'] == participants[0]['id']
    assert 'rigged' not in body
def update_participant(event):
    """
    Update a participant's name and/or url

    :param event: Lambda event containing the API Gateway request body including updated name or url and the
    path parameters wheel_id and participant_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel (DDB Hash Key)
        "participant_id": string ID of the participant (DDB Hash Key)
      },
      "body":
      {
        "id": string ID of the participant (DDB Hash Key),
        "name": string name of the wheel (optional),
        "url: Valid URL for the participant (optional),
      }
    }
    :return: response dictionary containing the updated participant object if successful
    {
      "body":
      {
        "id": string ID of the participant (DDB Hash Key),
        "wheel_id": string ID of the wheel (DDB Hash Key),
        "name": string name of the wheel,
        "url: URL for the participant,
        "created_at": creation timestamp,
        "updated_at": updated timestamp,
      }
    }
    """
    wheel_id = event['pathParameters']['wheel_id']
    participant_id = event['pathParameters']['participant_id']
    # Check that the participant exists
    participant = WheelParticipant.get_existing_item(Key={
        'id': participant_id,
        'wheel_id': wheel_id
    })
    body = event['body']
    params = {'updated_at': get_utc_timestamp()}
    if not check_string(body.get('name', 'Not Specified')) or not check_string(
            body.get('url', 'Not Specified')):
        raise base.BadRequestError(
            "Participants names and urls must be at least 1 character in length"
        )

    if 'name' in body:
        params['name'] = body['name']

    if 'url' in body:
        params['url'] = body['url']

    WheelParticipant.update_item(Key={
        'id': participant_id,
        'wheel_id': wheel_id
    },
                                 **to_update_kwargs(params))
    participant.update(params)
    return participant
Ejemplo n.º 3
0
def test_select_participant_removes_rigging(mock_dynamodb,
                                            mock_participant_table,
                                            mock_wheel_table):
    mock_wheel_table.update_item(Key={'id': WHEEL_ID},
                                 **to_update_kwargs({'rigging': {}}))

    participant = {
        'id': get_uuid(),
        'wheel_id': WHEEL_ID,
        'name': 'Pick me!',
        'url': 'https://amazon.com',
        'weight': 1
    }
    mock_participant_table.put_item(Item=participant)

    event = {
        'body': {},
        'pathParameters': {
            'wheel_id': WHEEL_ID,
            'participant_id': participant['id']
        }
    }
    response = wheel_participant.select_participant(event)

    assert response['statusCode'] == 201
    assert 'rigging' not in mock_wheel_table.get_existing_item(
        Key={'id': WHEEL_ID})
def rig_participant(event):
    """
    Rig the specified wheel for the specified participant.  Default behavior is comical rigging (hidden == False)
    but hidden can be specified to indicate deceptive rigging (hidden == True)

    :param event: Lambda event containing the API Gateway request path parameters wheel_id and participant_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel to rig (DDB Hash Key)
        "participant_id": string ID of the participant to rig (DDB Hash Key)
      },
      "body":
      {
        "hidden": boolean indicates deceptive rigging if True, comical if False
      }
    }
    :return: response dictionary
    """
    # By default, rigging the wheel isn't hidden but they can be
    wheel_id = event['pathParameters']['wheel_id']
    participant_id = event['pathParameters']['participant_id']
    hidden = bool(event['body'].get('hidden', False))
    update = {'rigging': {'participant_id': participant_id, 'hidden': hidden}}
    Wheel.update_item(Key={'id': wheel_id}, **to_update_kwargs(update))
Ejemplo n.º 5
0
def wrap_participant_creation(wheel, participant):
    participant['weight'] = 1
    yield
    Wheel.update_item(Key={'id': wheel['id']},
                      **to_update_kwargs({
                          'participant_count':
                          wheel['participant_count'] + 1
                      }))
Ejemplo n.º 6
0
def wrap_participant_creation(wheel, participant):
    participant['weight'] = 1
    yield
    count = 0
    with WheelParticipant.batch_writer() as batch:
        for p in WheelParticipant.iter_query(
                KeyConditionExpression=Key('wheel_id').eq(wheel['id'])):
            count += 1
    Wheel.update_item(Key={'id': wheel['id']},
                      **to_update_kwargs({'participant_count': count}))
Ejemplo n.º 7
0
def on_participant_deletion(wheel, participant):
    """
    Normalize the remaining participant weights to account for participant removal.
    The ratio is based on the following:
     1) The participant should be at weight=1 when it leaves the system (which is the same as it arrived)
     2) That difference should be split by the remaining participants proportional by weight
        This ensures that 'weight=0' participants are still at weight=0 and that the sum of all
        weights is equal to the number of participants, so new additions are treated fairly
    :param wheel: Wheel dictionary:
    {
      "id": string ID of the wheel (DDB Hash Key),
      "name": string name of the wheel,
      "participant_count": number of participants in the wheel,
    }
    :param participant: Participant dictionary:
    {
      "id": string ID of the wheel (DDB Hash Key),
      "name": string name of the wheel,
      "url": Participant's URL,
      "wheel_id": string ID of the wheel the participant belongs to,
    }
    :return: None
    """
    total_weight = participant['weight']
    for p in WheelParticipant.iter_query(
            KeyConditionExpression=Key('wheel_id').eq(wheel['id'])):
        total_weight += p['weight']

    weight = participant['weight']
    remaining_weight = total_weight - weight  # <-- no longer presumes existing weight balance via 'int(participant_count)'
    ratio = (1 + ((weight - 1) / remaining_weight)) if (
        remaining_weight != 0) else 1
    num_participants = Decimal(0)
    with WheelParticipant.batch_writer() as batch:
        for p in WheelParticipant.iter_query(
                KeyConditionExpression=Key('wheel_id').eq(wheel['id'])):
            if p['id'] != participant['id']:
                # This is cast to a string before turning into a decimal because of rounding/inexact guards in boto3
                p['weight'] = Decimal(
                    str(float(p['weight']) *
                        float(ratio))) if (remaining_weight != 0) else 1
                batch.put_item(Item=p)
                num_participants = num_participants + 1

    Wheel.update_item(Key={'id': wheel['id']},
                      **to_update_kwargs(
                          {'participant_count': num_participants}))
Ejemplo n.º 8
0
def select_participant(wheel, participant):
    """
    Register the selection of a participant by updating the weights of all participants for a given wheel
    :param wheel: Wheel dictionary:
    {
      "id": string ID of the wheel (DDB Hash Key),
      "name": string name of the wheel,
      "participant_count": number of participants in the wheel,
    }
    :param participant: Participant dictionary:
    {
      "id": string ID of the participant (DDB Hash Key),
      "name": string name of the participant,
      "url": Participant's URL,
      "wheel_id": string ID of the wheel the participant belongs to,
      "weight": participant's weight in the selection algorithm
    }
    :return: None
    """

    num_participants = 0
    total_weight = Decimal(0)
    for p in WheelParticipant.iter_query(
            KeyConditionExpression=Key('wheel_id').eq(wheel['id'])):
        num_participants = num_participants + 1
        total_weight += p['weight']

    # Factor is the number by which all weights must be multiplied
    # so total weight will be equal to the number of participants.
    factor = Decimal(num_participants) / total_weight

    if num_participants > 1:
        weight_share = participant['weight'] / Decimal(num_participants - 1)
        with WheelParticipant.batch_writer() as batch:
            # Redistribute and normalize the weights.
            for p in WheelParticipant.iter_query(
                    KeyConditionExpression=Key('wheel_id').eq(wheel['id'])):
                if p['id'] == participant['id']:
                    p['weight'] = 0
                else:
                    p['weight'] += Decimal(weight_share)
                    p['weight'] *= factor
                batch.put_item(Item=p)
    Wheel.update_item(Key={'id': wheel['id']},
                      **to_update_kwargs(
                          {'participant_count': num_participants}))
Ejemplo n.º 9
0
def reset_wheel(wheel):
    """
    Resets the weights of all participants in the wheel and updates the wheel's participant count
    :param wheel: Wheel dictionary:
    {
      "id": string ID of the wheel (DDB Hash Key),
      "name": string name of the wheel,
      "participant_count": number of participants in the wheel,
    }
    :return: None
    """
    count = 0
    with WheelParticipant.batch_writer() as batch:
        for p in WheelParticipant.iter_query(KeyConditionExpression=Key('wheel_id').eq(wheel['id'])):
            p['weight'] = get_sub_wheel_size(p['name'])
            batch.put_item(Item=p)
            count += 1
    Wheel.update_item(Key={'id': wheel['id']}, **to_update_kwargs({'participant_count': count}))
Ejemplo n.º 10
0
def update_wheel(event):
    """
    Update the name of the wheel and/or refresh its participant count

    :param event: Lambda event containing the API Gateway request path parameter wheel_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel (DDB Hash Key)
      },
      "body":
      {
        "id": string ID of the wheel (DDB Hash Key),
        "name": string name of the wheel,
      }
    }
    :return: response dictionary containing the updated wheel object if successful
    {
      "body":
      {
        "id": string ID of the wheel (DDB Hash Key),
        "name": string name of the wheel,
        "participant_count": number of participants in the wheel,
        "created_at": creation timestamp,
        "updated_at": updated timestamp,
      }
    }
    """
    wheel_id = event['pathParameters']['wheel_id']
    key = {'id': wheel_id}
    # Make sure wheel exists
    wheel = Wheel.get_existing_item(Key=key)
    name = event['body'].get('name', None)
    if not check_string(name):
        raise base.BadRequestError(
            "Updating a wheel requires a new name of at least 1 character in length"
        )

    update = {'name': name, 'updated_at': get_utc_timestamp()}
    Wheel.update_item(Key=key, **to_update_kwargs(update))
    # Represent the change locally for successful responses
    wheel.update(update)
    return wheel
Ejemplo n.º 11
0
def test_fix_incorrect_participant_count(mock_dynamodb, setup_data,
                                         mock_wheel_table):
    out_of_whack = 999
    wheel = setup_data['wheel']
    wheel_id = wheel['id']
    proper_participant_count = wheel['participant_count']

    # # # # We will first test this on a select_participant operation.

    #  Throw the participant count way out of whack.
    mock_wheel_table.update_item(Key={'id': wheel['id']},
                                 **to_update_kwargs(
                                     {'participant_count': out_of_whack}))

    participant_count = mock_wheel_table.query(KeyConditionExpression=Key(
        'id').eq(wheel['id']))['Items'][0].get('participant_count')

    # #  Ensure it's out of whack.
    assert abs(out_of_whack - participant_count) < epsilon

    #  Select a participant to cause correction of participant count.
    wheel = Wheel.get_existing_item(Key={'id': wheel_id})
    choice_algorithm.select_participant(wheel, setup_data['participants'][0])

    #  ...and ensure it's back into whack.
    participant_count = mock_wheel_table.query(KeyConditionExpression=Key(
        'id').eq(wheel['id']))['Items'][0].get('participant_count')

    assert abs(Decimal(proper_participant_count) - participant_count) < epsilon

    # # # # We will next test this on a delete_participant operation.

    #  Throw the participant count way out of whack.
    mock_wheel_table.update_item(Key={'id': wheel['id']},
                                 **to_update_kwargs(
                                     {'participant_count': out_of_whack}))

    participant_count = mock_wheel_table.query(KeyConditionExpression=Key(
        'id').eq(wheel['id']))['Items'][0].get('participant_count')

    # #  Ensure it's out of whack.
    assert abs(out_of_whack - participant_count) < epsilon

    #  Delete a participant to cause correction of participant count.
    event = {
        'body': {},
        'pathParameters': {
            'wheel_id': wheel_id,
            'participant_id': setup_data['participants'][0]['id']
        }
    }
    wheel_participant.delete_participant(event)

    # #  ...and ensure it's back into whack.
    participant_count = mock_wheel_table.query(KeyConditionExpression=Key(
        'id').eq(wheel['id']))['Items'][0].get('participant_count')

    assert abs((Decimal(proper_participant_count) - 1) -
               participant_count) < epsilon

    # # # # We will next test this on a create_participant operation.

    #  Throw the participant count way out of whack.
    mock_wheel_table.update_item(Key={'id': wheel['id']},
                                 **to_update_kwargs(
                                     {'participant_count': out_of_whack}))

    participant_count = mock_wheel_table.query(KeyConditionExpression=Key(
        'id').eq(wheel['id']))['Items'][0].get('participant_count')

    # #  Ensure it's out of whack.
    assert abs(out_of_whack - participant_count) < epsilon

    #  Add a participant to cause correction of participant count.
    event = {
        'pathParameters': {
            'wheel_id': wheel_id
        },
        'body': {
            'name': 'Ishmael-on-the-Sea',
            'url': 'https://amazon.com'
        }
    }
    wheel_participant.create_participant(event)

    # #  ...and ensure it's back into whack.
    participant_count = mock_wheel_table.query(KeyConditionExpression=Key(
        'id').eq(wheel['id']))['Items'][0].get('participant_count')

    assert abs((Decimal(proper_participant_count)) -
               participant_count) < epsilon