Esempio n. 1
0
def test_parse_module_url():
    from sasctl.tasks import _parse_module_url

    body = RestObj({'createdBy': 'sasdemo',
         'creationTimeStamp': '2019-08-26T15:16:42.900Z',
         'destinationName': 'maslocal',
         'id': '62cae262-7287-412b-8f1d-bd2a12c8b434',
         'links': [{'href': '/modelPublish/models/44d526bc-d513-4637-b8a7-72daee4a7730',
                    'method': 'GET',
                    'rel': 'up',
                    'type': 'application/vnd.sas.models.publishing.publish',
                    'uri': '/modelPublish/models/44d526bc-d513-4637-b8a7-72daee4a7730'},
                   {'href': '/modelPublish/models/44d526bc-d513-4637-b8a7-72daee4a7730/log',
                    'method': 'GET',
                    'rel': 'self',
                    'type': 'application/json',
                    'uri': '/modelPublish/models/44d526bc-d513-4637-b8a7-72daee4a7730/log'}],
         'log': 'SUCCESS==={"links":[{"method":"GET","rel":"up","href":"/microanalyticScore/jobs","uri":"/microanalyticScore/jobs","type":"application/vnd.sas.collection","itemType":"application/vnd.sas.microanalytic.job"},{"method":"GET","rel":"self","href":"/microanalyticScore/jobs/465ecad8-cfd0-4403-ac8a-e49cd248fae3","uri":"/microanalyticScore/jobs/465ecad8-cfd0-4403-ac8a-e49cd248fae3","type":"application/vnd.sas.microanalytic.job"},{"method":"GET","rel":"source","href":"/microanalyticScore/jobs/465ecad8-cfd0-4403-ac8a-e49cd248fae3/source","uri":"/microanalyticScore/jobs/465ecad8-cfd0-4403-ac8a-e49cd248fae3/source","type":"application/vnd.sas.microanalytic.module.source"},{"method":"GET","rel":"submodules","href":"/microanalyticScore/jobs/465ecad8-cfd0-4403-ac8a-e49cd248fae3/submodules","uri":"/microanalyticScore/jobs/465ecad8-cfd0-4403-ac8a-e49cd248fae3/submodules","type":"application/vnd.sas.collection","itemType":"application/vnd.sas.microanalytic.submodule"},{"method":"DELETE","rel":"delete","href":"/microanalyticScore/jobs/465ecad8-cfd0-4403-ac8a-e49cd248fae3","uri":"/microanalyticScore/jobs/465ecad8-cfd0-4403-ac8a-e49cd248fae3"},{"method":"GET","rel":"module","href":"/microanalyticScore/modules/decisiontree","uri":"/microanalyticScore/modules/decisiontree","type":"application/vnd.sas.microanalytic.module"}],"version":1,"createdBy":"sasdemo","creationTimeStamp":"2019-08-26T15:16:42.857Z","modifiedBy":"sasdemo","modifiedTimeStamp":"2019-08-26T15:16:48.988Z","id":"465ecad8-cfd0-4403-ac8a-e49cd248fae3","moduleId":"decisiontree","state":"completed","errors":[]}',
         'modelId': '459aae0d-d64f-4376-94e7-be31911f4bdb',
         'modelName': 'DecisionTree',
         'modifiedBy': 'sasdemo',
         'modifiedTimeStamp': '2019-08-26T15:16:49.315Z',
         'publishName': 'Decision Tree',
         'version': 1})

    msg = body.get('log').lstrip('SUCßCESS===')
    assert _parse_module_url(msg) == '/microanalyticScore/modules/decisiontree'
 def side_effect(_, link, **kwargs):
     assert 'limit=%d' % limit in link
     start = int(re.search(r'(?<=start=)[\d]+', link).group())
     if start == 10:
         return RestObj(items=pages[1])
     elif start == 20:
         return RestObj(items=pages[2])
     else:
         return RestObj(items=[])
Esempio n. 3
0
def test_list_items():
    from sasctl.core import _build_crud_funcs, RestObj

    list_items, _, _, _ = _build_crud_funcs('/items')

    with mock.patch('sasctl.core.request') as request:
        request.return_value = RestObj()

        resp = list_items()

    assert request.call_count == 1
    assert [RestObj()] == resp
def test_getitem_no_paging():
    items = [{'name': 'a'}, {'name': 'b'}, {'name': 'c'}]
    obj = RestObj(items=items, count=len(items))

    with mock.patch('sasctl.core.request') as request:
        l = PagedList(obj)

        for i in range(len(l)):
            item = l[i]
            assert RestObj(items[i]) == item

    # No request should have been made to retrieve additional data.
    request.assert_not_called()
def test_len_no_paging():
    items = [{'name': 'a'}, {'name': 'b'}, {'name': 'c'}]
    obj = RestObj(items=items, count=len(items))

    with mock.patch('sasctl.core.request') as request:
        l = PagedList(obj)
        assert len(l) == 3

        for i, o in enumerate(l):
            assert RestObj(items[i]) == o

    # No request should have been made to retrieve additional data.
    request.assert_not_called()
def test_no_paging_required():
    """If "next" link not present, current items should be included."""

    items = [{'name': 'a'}, {'name': 'b'}, {'name': 'c'}]
    obj = RestObj(items=items, count=len(items))

    with mock.patch('sasctl.core.request') as request:
        pager = PagedItemIterator(obj)

        for i, o in enumerate(pager):
            assert RestObj(items[i]) == o

    # No request should have been made to retrieve additional data.
    request.assert_not_called()
def test_is_iterator():
    """PagedItemIterator should be an iterator itself."""
    items = [{'name': 'a'}, {'name': 'b'}, {'name': 'c'}]
    obj = RestObj(items=items, count=len(items))

    with mock.patch('sasctl.core.request') as request:
        pager = PagedItemIterator(obj)

        for i in range(len(items)):
            o = next(pager)
            assert RestObj(items[i]) == o

    # No request should have been made to retrieve additional data.
    request.assert_not_called()
def test_convert_to_list():
    """Converts correctly to a list."""
    items = [{'name': 'a'}, {'name': 'b'}, {'name': 'c'}]
    obj = RestObj(items=items, count=len(items))

    with mock.patch('sasctl.core.request') as request:
        pager = PagedItemIterator(obj)

        # Can convert to list
        target = [RestObj(i) for i in items]
        assert list(pager) == target

    # No request should have been made to retrieve additional data.
    request.assert_not_called()
Esempio n. 9
0
def test_bugfix_27():
    """NaN values should be converted to null before being sent to MAS

    https://github.com/sassoftware/python-sasctl/issues/27
    """

    import io
    from sasctl.core import RestObj
    from sasctl.services import microanalytic_score as mas
    pd = pytest.importorskip('pandas')

    df = pd.read_csv(io.StringIO('\n'.join([
        u'BAD,LOAN,MORTDUE,VALUE,REASON,JOB,YOJ,DEROG,DELINQ,CLAGE,NINQ,CLNO,DEBTINC',
        u'0,1.0,1100.0,25860.0,39025.0,HomeImp,Other,10.5,0.0,0.0,94.36666667,1.0,9.0,',
        u'1,1.0,1300.0,70053.0,68400.0,HomeImp,Other,7.0,0.0,2.0,121.8333333,0.0,14.0,'
    ])))

    with mock.patch('sasctl._services.microanalytic_score.MicroAnalyticScore.get_module') as get_module:
        get_module.return_value = RestObj({
            'name': 'Mock Module',
            'id': 'mockmodule'
        })
        with mock.patch('sasctl._services.microanalytic_score.MicroAnalyticScore.post') as post:
            x = df.iloc[0, :]
            mas.execute_module_step('module', 'step', **x)

    # Ensure we're passing NaN to execute_module_step
    assert pd.isna(x['DEBTINC'])

    # Make sure the value has been converted to None before being serialized to JSON.
    # This ensures that the JSON value will be null.
    json = post.call_args[1]['json']
    inputs = json['inputs']
    debtinc = [i for i in inputs if i['name'] == 'DEBTINC'].pop()
    assert debtinc['value'] is None
def test_no_paging_required():
    """If "next" link not present, current items should be included."""

    items = [{'name': 'a'}, {'name': 'b'}, {'name': 'c'}]
    obj = RestObj(items=items, count=len(items))

    with mock.patch('sasctl.core.request') as request:
        pager = PageIterator(obj)

        # Returned page of items should preserve item order
        items = next(pager)
        for idx, item in enumerate(items):
            assert item.name == RestObj(items[idx]).name

    # No request should have been made to retrieve additional data.
    request.assert_not_called()
Esempio n. 11
0
def test_save_performance_project_types():
    from sasctl.tasks import update_model_performance

    with mock.patch(
            'sasctl._services.model_repository.ModelRepository.get_model'
    ) as model:
        with mock.patch(
                'sasctl._services.model_repository.ModelRepository.get_project'
        ) as project:
            model.return_value = RestObj(name='fakemodel', projectId=1)

            # Function is required
            with pytest.raises(ValueError):
                project.return_value = {}
                update_model_performance(None, None, None)

            # Target Level is required
            with pytest.raises(ValueError):
                project.return_value = {'function': 'Prediction'}
                update_model_performance(None, None, None)

            # Prediction variable required
            with pytest.raises(ValueError):
                project.return_value = {
                    'function': 'Prediction',
                    'targetLevel': 'Binary'
                }
                update_model_performance(None, None, None)
def test_paging_inflated_count():
    """Test behavior when server overestimates the number of items available."""
    import re

    start = 10
    limit = 10

    # Only defines 20 items to return
    pages = [
        [{
            'name': x
        } for x in list('abcdefghi')],
        [{
            'name': x
        } for x in list('klmnopqrs')],
        [{
            'name': x
        } for x in list('uv')],
    ]
    actual_num_items = sum(len(page) for page in pages)

    # services (like Files) may overestimate how many items are available.
    # Simulate that behavior
    num_items = 23

    obj = RestObj(items=pages[0],
                  count=num_items,
                  links=[{
                      'rel':
                      'next',
                      'href':
                      '/moaritems?start=%d&limit=%d' % (start, limit)
                  }])

    with mock.patch('sasctl.core.request') as req:

        def side_effect(_, link, **kwargs):
            assert 'limit=%d' % limit in link
            start = int(re.search(r'(?<=start=)[\d]+', link).group())
            if start == 10:
                return RestObj(items=pages[1])
            elif start == 20:
                return RestObj(items=pages[2])
            else:
                return RestObj(items=[])

        req.side_effect = side_effect

        pager = PagedItemIterator(obj, threads=1)

        # Initially, length is estimated based on how many items the server says it has
        assert len(pager) == num_items

        # Retrieve all of the items
        items = [x for x in pager]

    assert len(items) == actual_num_items
    assert len(pager) == num_items - actual_num_items
def test_paging(paging):
    """Test that correct paging requests are made."""

    obj, items, _ = paging

    pager = PagedItemIterator(obj)

    for i, o in enumerate(pager):
        assert RestObj(items[i]) == o
Esempio n. 14
0
def test_update_item():
    from sasctl.core import _build_crud_funcs, RestObj

    _, _, update_item, _ = _build_crud_funcs('/widget')

    target = RestObj({'name': 'Test Widget', 'id': 12345})

    with mock.patch('sasctl.core.request') as request:
        request.return_value = target

        # ETag should be required
        with pytest.raises(ValueError):
            resp = update_item(target)

        target._headers = {'etag': 'abcd'}
        resp = update_item(target)
    assert request.call_count == 1
    assert ('put', '/widget/12345') == request.call_args[0]
    assert target == resp
Esempio n. 15
0
def test_zip_paging(paging):
    """Check that zip() works correctly with the list."""
    obj, items, _ = paging
    l = PagedList(obj)

    # length of list should equal total # of items
    assert len(l) == len(items)

    for target, actual in zip(items, l):
        assert RestObj(target).name == actual.name
Esempio n. 16
0
def test_getitem_paging(paging):
    """Check that list can be enumerated."""
    obj, items, _ = paging
    l = PagedList(obj)

    # length of list should equal total # of items
    assert len(l) == len(items)

    for i, item in enumerate(l):
        assert item.name == RestObj(items[i]).name
Esempio n. 17
0
def test_put_restobj():
    from sasctl.core import put, RestObj

    url = "/jobDefinitions/definitions/717331fa-f650-4e31-b9e2-6e6d49f66bf9"
    obj = RestObj({'_headers': {'etag': 123, 'content-type': 'spam'}})

    # Base case
    with mock.patch('sasctl.core.request') as req:
        put(url, obj)

    assert req.called
    args = req.call_args[0]
    kwargs = req.call_args[1]

    assert args == ('put', url)
    assert kwargs['json'] == obj
    assert kwargs['headers']['If-Match'] == 123
    assert kwargs['headers']['Content-Type'] == 'spam'

    # Should merge with explicit headers
    with mock.patch('sasctl.core.request') as req:
        put(url, obj, headers={'encoding': 'spammy'})

    assert req.called
    args = req.call_args[0]
    kwargs = req.call_args[1]

    assert args == ('put', url)
    assert kwargs['json'] == obj
    assert kwargs['headers']['If-Match'] == 123
    assert kwargs['headers']['Content-Type'] == 'spam'
    assert kwargs['headers']['encoding'] == 'spammy'

    # Should not overwrite explicit headers
    with mock.patch('sasctl.core.request') as req:
        put(url,
            obj,
            headers={
                'Content-Type': 'notspam',
                'encoding': 'spammy'
            })

    assert req.called
    args = req.call_args[0]
    kwargs = req.call_args[1]

    assert args == ('put', url)
    assert kwargs['json'] == obj
    assert kwargs['headers']['If-Match'] == 123
    assert kwargs['headers']['Content-Type'] == 'notspam'
    assert kwargs['headers']['encoding'] == 'spammy'
Esempio n. 18
0
def test_copy(paging):
    """Check that [:] syntax works correctly with the list."""
    obj, items, _ = paging
    l = PagedList(obj)

    # length of list should equal total # of items
    assert len(l) == len(items)

    target = items[:]
    actual = l[:]

    assert len(actual) == len(l)

    for i, item in enumerate(actual):
        assert item.name == RestObj(target[i]).name
def test_paging_required(paging):
    """Requests should be made to retrieve additional pages."""
    obj, items, _ = paging

    pager = PageIterator(obj)
    init_count = pager._start

    for i, page in enumerate(pager):
        for j, item in enumerate(page):
            if i == 0:
                item_idx = j
            else:
                # Account for initial page size not necessarily being same size
                # as additional pages
                item_idx = init_count + (i-1) * pager._limit + j
            target = RestObj(items[item_idx])
            assert item.name == target.name
Esempio n. 20
0
def test_slice_paging(paging):
    """Check that [i:j] syntax works correctly with the list."""

    obj, items, _ = paging
    l = PagedList(obj)

    # length of list should equal total # of items
    assert len(l) == len(items)

    # Generate pairs of start:stop indexes that intentionally exceed
    # the size of the array, and include empty sequences.
    starts = range(len(l) + 1)
    stops = range(len(l), -1, -1)

    for start, stop in zip(starts, stops):
        target = items[start:stop]
        actual = l[start:stop]

        for i, item in enumerate(actual):
            assert item.name == RestObj(target[i]).name
def paging(request):
    """Create a RestObj designed to page through a collection of items and the
    collection itself.

    Returns
    -------
    RestObj : initial RestObj that can be used to initialize a paging iterator
    List[dict] : List of items being used as the "server-side" source
    MagicMock : Mock of sasctl.request for performing additional validation

    """
    import math
    import re

    num_items, start, limit = request.param

    with mock.patch('sasctl.core.request') as req:
        items = [{'name': str(i)} for i in range(num_items)]

        obj = RestObj(items=items[:start],
                      count=len(items),
                      links=[{'rel': 'next',
                              'href': '/moaritems?start=%d&limit=%d' % (
                                  start, limit)}])

        def side_effect(_, link, **kwargs):
            assert 'limit=%d' % limit in link
            start = int(re.search(r'(?<=start=)[\d]+', link).group())
            return RestObj(items=items[start:start + limit])

        req.side_effect = side_effect
        yield obj, items[:], req

    # Enough requests should have been made to retrieve all the data.
    # Additional requests may have been made by workers to non-existent pages.
    call_count = (num_items - start) / float(limit)
    assert req.call_count >= math.ceil(call_count)
Esempio n. 22
0
def test_str():
    """Check str formatting of list."""
    source_items = [{'name': 'a'}, {'name': 'b'}, {'name': 'c'},
                    {'name': 'd'}, {'name': 'e'}, {'name': 'f'}]

    start = 2
    limit = 2

    with mock.patch('sasctl.core.request') as req:
        obj = RestObj(items=source_items[:2],
                      count=len(source_items),
                      links=[{'rel': 'next',
                              'href': '/moaritems?start=%d&limit=%d' % (
                                  start, limit)}])

        def side_effect(_, link, **kwargs):
            if 'start=2' in link:
                result = source_items[1:1+limit]
            elif 'start=4' in link:
                result =  source_items[3:3+limit]
            return RestObj(items=result)

        req.side_effect = side_effect

        l = PagedList(obj)

        for i in range(len(source_items)):
            # Force access of each item to ensure it's downloaded
            _ = l[i]

            if i < len(source_items) - 1:
                # Ellipses should indicate unfetched results unless we're
                # at the end of the list
                assert str(l).endswith(', ... ]')
            else:
                assert not str(l).endswith(', ... ]')
def test_define_steps_invalid_name():
    """Verify that invalid characters are stripped."""

    # Mock module to be returned
    # From bug reported by Paata
    module = RestObj({
        'createdBy':
        'paata',
        'creationTimeStamp':
        '2020-03-02T15:37:57.811Z',
        'id':
        'petshop_model_xgb_new',
        'language':
        'ds2',
        'links': [{
            'href': '/microanalyticScore/modules',
            'itemType': 'application/vnd.sas.microanalytic.module',
            'method': 'GET',
            'rel': 'up',
            'type': 'application/vnd.sas.collection',
            'uri': '/microanalyticScore/modules'
        }, {
            'href': '/microanalyticScore/modules/petshop_model_xgb_new',
            'method': 'GET',
            'rel': 'self',
            'type': 'application/vnd.sas.microanalytic.module',
            'uri': '/microanalyticScore/modules/petshop_model_xgb_new'
        }, {
            'href':
            '/microanalyticScore/modules/petshop_model_xgb_new/source',
            'method':
            'GET',
            'rel':
            'source',
            'type':
            'application/vnd.sas.microanalytic.module.source',
            'uri':
            '/microanalyticScore/modules/petshop_model_xgb_new/source'
        }, {
            'href':
            '/microanalyticScore/modules/petshop_model_xgb_new/steps',
            'itemType':
            'application/vnd.sas.microanalytic.module.step',
            'method':
            'GET',
            'rel':
            'steps',
            'type':
            'application/vnd.sas.collection',
            'uri':
            '/microanalyticScore/modules/petshop_model_xgb_new/steps'
        }, {
            'href':
            '/microanalyticScore/modules/petshop_model_xgb_new/submodules',
            'itemType':
            'application/vnd.sas.microanalytic.submodule',
            'method':
            'GET',
            'rel':
            'submodules',
            'type':
            'application/vnd.sas.collection',
            'uri':
            '/microanalyticScore/modules/petshop_model_xgb_new/submodules'
        }, {
            'href': '/microanalyticScore/modules/petshop_model_xgb_new',
            'method': 'PUT',
            'rel': 'update',
            'responseType': 'application/vnd.sas.microanalytic.module',
            'type': 'application/vnd.sas.microanalytic.module',
            'uri': '/microanalyticScore/modules/petshop_model_xgb_new'
        }, {
            'href': '/microanalyticScore/modules/petshop_model_xgb_new',
            'method': 'DELETE',
            'rel': 'delete',
            'uri': '/microanalyticScore/modules/petshop_model_xgb_new'
        }],
        'modifiedBy':
        'paata',
        'modifiedTimeStamp':
        '2020-03-02T15:57:10.008Z',
        'name':
        '"petshop_model_XGB_new"',
        'properties': [{
            'name':
            'sourceURI',
            'value':
            'http://modelmanager/modelRepository/models/72facedf-8e36-418e-8145-1398686b997a'
        }],
        'revision':
        0,
        'scope':
        'public',
        'stepIds': ['score'],
        'version':
        2,
        'warnings': []
    })

    # Mock module step with multiple inputs
    step2 = RestObj({
        "id":
        "score",
        "inputs": [{
            "name": "age",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "b",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "chas",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "crim",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "dis",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "indus",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "lstat",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "nox",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "ptratio",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "rad",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "rm",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "tax",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "zn",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }],
        "outputs": [{
            "name": "em_prediction",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "p_price",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "_warn_",
            "type": "string",
            "dim": 0,
            "size": 4
        }]
    })

    with mock.patch(
            'sasctl._services.microanalytic_score.MicroAnalyticScore.get_module'
    ) as get_module:
        with mock.patch(
                'sasctl._services.microanalytic_score.MicroAnalyticScore'
                '.get_module_step') as get_step:
            get_module.return_value = module
            get_step.side_effect = [step2]
            result = mas.define_steps(None)

    for step in get_step.side_effect:
        assert hasattr(result, step.id)
Esempio n. 24
0
def test_define_steps():

    # Mock module to be returned
    module = RestObj(name='unittestmodule', stepIds=['step1', 'step2'])

    # Mock module step with no inputs
    step1 = RestObj(id='post')

    # Mock module step with multiple inputs
    step2 = RestObj({
        "id":
        "score",
        "inputs": [{
            "name": "age",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "b",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "chas",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "crim",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "dis",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "indus",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "lstat",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "nox",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "ptratio",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "rad",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "rm",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "tax",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "zn",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }],
        "outputs": [{
            "name": "em_prediction",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "p_price",
            "type": "decimal",
            "dim": 0,
            "size": 0
        }, {
            "name": "_warn_",
            "type": "string",
            "dim": 0,
            "size": 4
        }]
    })

    with mock.patch(
            'sasctl._services.microanalytic_score.MicroAnalyticScore.get_module'
    ) as get_module:
        with mock.patch(
                'sasctl._services.microanalytic_score.MicroAnalyticScore'
                '.get_module_step') as get_step:
            get_module.return_value = module
            get_step.side_effect = [step1, step2]
            result = mas.define_steps(None)

    for step in get_step.side_effect:
        assert hasattr(result, step.id)
Esempio n. 25
0
def test_get_item_inflated_len():
    """Test behavior when server overestimates the number of items available."""
    import re

    start = 10
    limit = 10

    # Only defines 20 items to return
    pages = [
        [{'name': x} for x in list('abcdefghi')],
        [{'name': x} for x in list('klmnopqrs')],
        [{'name': x} for x in list('uv')],
    ]
    actual_num_items = sum(len(page) for page in pages)

    # services (like Files) may overestimate how many items are available.
    # Simulate that behavior
    num_items = 23

    obj = RestObj(items=pages[0],
                  count=num_items,
                  links=[{'rel': 'next',
                          'href': '/moaritems?start=%d&limit=%d' % (start, limit)}])

    with mock.patch('sasctl.core.request') as req:
        def side_effect(_, link, **kwargs):
            assert 'limit=%d' % limit in link
            start = int(re.search(r'(?<=start=)[\d]+', link).group())
            if start == 10:
                return RestObj(items=pages[1])
            elif start == 20:
                return RestObj(items=pages[2])
            else:
                return RestObj(items=[])

        req.side_effect = side_effect
        pager = PagedList(obj, threads=1)

        # Initially, length is estimated based on how many items the server says it has available
        assert len(pager) == num_items

        # Retrieve all of the items
        items = [x for x in pager]

    # Should have retrieved all of the items
    assert len(items) == actual_num_items

    # List length should now be updated to indicate the correct number of items
    assert len(pager) == actual_num_items

    # Recreate the pager
    with mock.patch('sasctl.core.request') as req:
        def side_effect(_, link, **kwargs):
            assert 'limit=%d' % limit in link
            start = int(re.search(r'(?<=start=)[\d]+', link).group())
            if start == 10:
                return RestObj(items=pages[1])
            elif start == 20:
                return RestObj(items=pages[2])
            else:
                return RestObj(items=[])

        req.side_effect = side_effect

        pager = PagedList(obj, threads=1)

        # Requesting the last item should work & cause loading of all items
        last_item = pager[-1]

    # Make sure the last item is correct (even though server inflated item count)
    assert last_item == pages[-1][-1]
    assert len(pager) == actual_num_items
 def side_effect(_, link, **kwargs):
     assert 'limit=%d' % limit in link
     start = int(re.search(r'(?<=start=)[\d]+', link).group())
     return RestObj(items=items[start:start + limit])
Esempio n. 27
0
 def side_effect(_, link, **kwargs):
     if 'start=2' in link:
         result = source_items[1:1+limit]
     elif 'start=4' in link:
         result =  source_items[3:3+limit]
     return RestObj(items=result)