コード例 #1
0
ファイル: test_db.py プロジェクト: testing-farm/artemis
def test_transaction(caplog: _pytest.logging.LogCaptureFixture,
                     logger: gluetool.log.ContextAdapter, db: DB) -> None:
    """
    Test whether :py:func:`transaction` behaves correctly when wrapping non-conflicting queries.
    """

    with db.get_session(transactional=True) as session:
        update = tft.artemis.tasks._guest_state_update_query(
            'dummy-guest',
            GuestState.PROVISIONING,
            current_state=GuestState.ROUTING).unwrap()

        insert = sqlalchemy.insert(
            GuestEvent.__table__).values(  # type: ignore[attr-defined]
                updated=datetime.datetime.utcnow(),
                guestname='dummy-guest',
                eventname='dummy-event')

        with transaction() as r:
            session.execute(update)
            session.execute(insert)

        assert r.success is True

    requests = SafeQuery.from_session(session, GuestRequest).all().unwrap()

    assert len(requests) == 1
    # TODO: cast shouldn't be needed, sqlalchemy should annouce .state as enum - maybe with more recent stubs?
    assert cast(GuestState, requests[0].state) == GuestState.PROVISIONING

    events = SafeQuery.from_session(session, GuestEvent).all().unwrap()

    assert len(events) == 1
    assert events[0].guestname == 'dummy-guest'
    assert events[0].eventname == 'dummy-event'
コード例 #2
0
ファイル: test_db.py プロジェクト: testing-farm/artemis
def test_transaction_no_transactions(
        caplog: _pytest.logging.LogCaptureFixture,
        logger: gluetool.log.ContextAdapter,
        session: sqlalchemy.orm.session.Session) -> None:
    """
    Test whether :py:func:`transaction` behaves correctly when facing non-transactional session.
    """

    query1 = sqlalchemy.insert(Counters.__table__).values(name='counter1',
                                                          count=1)

    query2 = sqlalchemy.insert(Counters.__table__).values(name='counter2',
                                                          count=2)

    with transaction() as r:
        session.execute(query1)
        session.execute(query2)

    assert r.success is True

    records = SafeQuery.from_session(session, Counters).order_by(
        Counters.name).all().unwrap()

    assert len(records) == 2
    assert records[0].name == 'counter1'
    assert records[0].count == 1
    assert records[1].name == 'counter2'
    assert records[1].count == 2
コード例 #3
0
ファイル: test_db.py プロジェクト: testing-farm/artemis
def test_safe_query_get_error(caplog: _pytest.logging.LogCaptureFixture,
                              logger: gluetool.log.ContextAdapter,
                              session: sqlalchemy.orm.session.Session) -> None:
    """
    Test handling of genuine SQLAlchemy error: without any schema or records, fetch a record.
    """

    r = SafeQuery.from_session(session,
                               Counters).filter(Counters.name == 'bar').all()

    assert r.is_ok is False

    failure = r.unwrap_error()

    assert 'query' in failure.details
    assert failure.exception is not None

    failure.handle(logger)

    assert_failure_log(
        caplog,
        'failed to retrieve query result',
        exception_label=
        r'OperationalError: \(sqlite3\.OperationalError\) no such table: counters'
    )
コード例 #4
0
ファイル: test_db.py プロジェクト: testing-farm/artemis
def test_schema_actual_load(session: sqlalchemy.orm.session.Session) -> None:
    """
    Metatest of sorts: doesn't test any unit nor scenario, but a fixture. If everything went well, ``_schema_actual``
    was successfull and created the full DB schema in our current DB fixture. We are not interested in testing
    whether the schema is sane or whether it matches models, no, we only want to be sure the schema fixture works,
    and at least something resembling the Artemis DB schema has been created.
    """

    assert SafeQuery.from_session(session, GuestRequest).all().unwrap() == []
コード例 #5
0
ファイル: test_db.py プロジェクト: testing-farm/artemis
def test_safe_db_change(logger: gluetool.log.ContextAdapter,
                        session: sqlalchemy.orm.session.Session) -> None:
    r = safe_db_change(
        logger, session,
        sqlalchemy.update(
            Counters.__table__).where(Counters.name == 'foo').values(count=1))

    assert r.is_error is False
    assert r.unwrap() is True

    records = SafeQuery.from_session(session, Counters).all().unwrap()

    assert len(records) == 1
    assert records[0].count == 1
コード例 #6
0
ファイル: test_db.py プロジェクト: testing-farm/artemis
def test_safe_query_query(logger: gluetool.log.ContextAdapter,
                          session: sqlalchemy.orm.session.Session) -> None:
    """
    Test regular workflow: construct query, fetch records.
    """

    r = SafeQuery.from_session(session, Counters).all()

    assert r.is_ok is True

    records = r.unwrap()

    assert len(records) == 1
    assert records[0].name == 'foo'
    assert records[0].count == 0
コード例 #7
0
ファイル: test_db.py プロジェクト: testing-farm/artemis
def test_safe_db_change_multiple(
        logger: gluetool.log.ContextAdapter,
        session: sqlalchemy.orm.session.Session) -> None:
    r = safe_db_change(logger,
                       session,
                       sqlalchemy.update(Counters.__table__).values(count=1),
                       expected_records=2)

    assert r.is_error is False
    assert r.unwrap() is True

    records = SafeQuery.from_session(session, Counters).all().unwrap()

    assert len(records) == 2
    assert all([record.count == 1 for record in records])
コード例 #8
0
ファイル: test_db.py プロジェクト: testing-farm/artemis
def assert_upsert_counter(session: sqlalchemy.orm.session.Session,
                          count: int,
                          subname: str = '',
                          subcount: int = 0) -> Counters:
    records = SafeQuery.from_session(session, Counters).all().unwrap()

    assert len(records) == 1

    record = records[0]

    assert record.name == 'foo'
    assert record.count == count
    assert record.subname == subname
    assert record.subcount == subcount

    return record
コード例 #9
0
ファイル: test_db.py プロジェクト: testing-farm/artemis
def test_safe_query_no_change_on_error(
        caplog: _pytest.logging.LogCaptureFixture,
        logger: gluetool.log.ContextAdapter,
        session: sqlalchemy.orm.session.Session,
        monkeypatch: _pytest.monkeypatch.MonkeyPatch) -> None:
    """
    If query already encoutered an error, it should not apply any additional methods nor return any valid records.
    """

    mock_failure = MagicMock(name='Failure<mock>')

    query = SafeQuery.from_session(session, Counters)

    # Inject a failure
    query.failure = mock_failure

    # Now these should do nothing, and return an error.
    r = query.filter(Counters.name == 'bar').all()

    assert r.is_ok is False

    failure = r.unwrap_error()

    assert failure is mock_failure
コード例 #10
0
ファイル: test_db.py プロジェクト: testing-farm/artemis
def test_transaction_conflict(caplog: _pytest.logging.LogCaptureFixture,
                              logger: gluetool.log.ContextAdapter, db: DB,
                              session: sqlalchemy.orm.session.Session) -> None:
    """
    Test whether :py:func:`transaction` intercepts and reports transaction rollback.
    """

    with db.get_session(transactional=True) as session2, db.get_session(
            transactional=True) as session3:
        update1 = tft.artemis.tasks._guest_state_update_query(
            'dummy-guest',
            GuestState.PROVISIONING,
            current_state=GuestState.ROUTING).unwrap()

        update2 = tft.artemis.tasks._guest_state_update_query(
            'dummy-guest',
            GuestState.PROMISED,
            current_state=GuestState.ROUTING).unwrap()

        insert1 = sqlalchemy.insert(
            GuestEvent.__table__).values(  # type: ignore[attr-defined]
                updated=datetime.datetime.utcnow(),
                guestname='dummy-guest',
                eventname='dummy-event')

        insert2 = sqlalchemy.insert(
            GuestEvent.__table__).values(  # type: ignore[attr-defined]
                updated=datetime.datetime.utcnow(),
                guestname='dummy-guest',
                eventname='another-dummy-event')

        # To create conflict, we must "initialize" view of both sessions, by executing a query. This will setup
        # their initial knowledge - without this step, the second transaction wouldn't run into any conflict because
        # it would issue its first query when the first transaction has been already committed.
        #
        # Imagine two tasks, both loading guest request from DB, then making some decisions, eventually both
        # trying to change it. The initial DB query sets the stage for both transactions seeing the same DB
        # state, and only one is allowed to modify the records both touched.
        SafeQuery.from_session(session2, GuestRequest).all()
        SafeQuery.from_session(session3, GuestRequest).all()

        with transaction() as r1:
            session2.execute(update1)
            session2.execute(insert1)

        session2.commit(
        )  # type: ignore[no-untyped-call]  # TODO: untyped commit()??

        assert r1.success is True

        with transaction() as r2:
            session3.execute(update2)
            session3.execute(insert2)

        session2.commit(
        )  # type: ignore[no-untyped-call]  # TODO: untyped commit()??

        assert r2.success is False

    requests = SafeQuery.from_session(session, GuestRequest).all().unwrap()

    assert len(requests) == 1
    # TODO: cast shouldn't be needed, sqlalchemy should annouce .state as enum - maybe with more recent stubs?
    assert cast(GuestState, requests[0].state) == GuestState.PROVISIONING

    events = SafeQuery.from_session(session, GuestEvent).all().unwrap()

    assert len(events) == 1
    assert events[0].guestname == 'dummy-guest'
    assert events[0].eventname == 'dummy-event'