diff options
| author | eunhhu <[email protected]> | 2026-09-28 04:51:16 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-28 04:51:16 +0100 |
| commit | adc4e5724c926cac4ecb8dc3a58a5f9b4d1b9b81 (patch) | |
| tree | c57cb746653c3bbb9eff35d509e7b71c353b9301 /tests | |
| parent | 9542bf3faa27373076cb4b31dac01c34570962fb (diff) | |
| download | pure-adc4e5724c926cac4ecb8dc3a58a5f9b4d1b9b81.tar.gz pure-adc4e5724c926cac4ecb8dc3a58a5f9b4d1b9b81.zip | |
Take a view's arguments from the request's query
GET /views/name?argument=value gives a view that takes arguments its
arguments: the query as it came is kept on the request's work slot (it
outlives the connection, which may go before the view is shown), and when
the view is shown, on the connection thread from a snapshot or by the
owner, its pairs (values %XX decoded) are laid out as a payload of the
arguments' shape and given before the view is evaluated. from and count
still page a table. An argument missing, unknown, given twice, not of its
type or badly escaped answers 400 invalid. A registered query takes its
view's arguments the same way (GET /queries/name?k=4). An arguments input
is not an input to post to.
server_arguments opens 200 accounts, then asks 83 times against a model:
balances by key (404 no_value for a key not there), counts above an
argument, texts with &, =, % and non-ASCII shown back, a table filtered by
two arguments whose column folds with one, paged, counts by an owner's
name, a registered query with an argument, and six refusals. The format's
document (008) describes arguments.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/check.py | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/tests/check.py b/tests/check.py index cc33947..2020363 100644 --- a/tests/check.py +++ b/tests/check.py @@ -1180,6 +1180,107 @@ def server_rows(aup, pure, work): 'orders_applied': applied, 'orders_refused': refused, 'logged': len(log)} +ARGUED = ''' +record Account { + aid = 0 + bid = 0 + balance = 0 + owner: text +} +state accounts: list of Account by aid +input open = message { + aid: u32 + bid: u32 + balance: number + owner: text +} +on open { accounts.add(Account { aid: open.aid, bid: open.bid, balance: open.balance, owner: open.owner }) } + +view balance(aid: u32) = accounts[aid].balance +view richer(low: number) = count(a in accounts where a.balance > low) +view hello(name: text, times: u32) = "{name} x{times}" +view branch(b: u32, over: number) = each a in accounts where a.bid == b && a.balance > over { + aid = a.aid + share = a.balance / sum(x.balance for x in accounts where x.bid == b) + owner = a.owner +} +view owned(who: text) = count(a in accounts where a.owner == who) +''' + + +def server_arguments(aup, pure, work): + """Views that take arguments, given as the request's query (values %XX + decoded): a keyed lookup, a count above an argument, texts shown back, + a table filtered by two arguments whose column folds with one of them, + paged; a registered query that takes one. Every answer against a model; + an argument missing, unknown or not of its type is 400 invalid.""" + import http.client + import random + import urllib.parse + source, module = work / 'argued.aup', work / 'argued.pmod' + source.write_text(ARGUED) + compile_module(aup, source, module) + host = HttpHost([pure / 'build/pure-serve', '--module', module, '--store', work / 'argued-store']) + + def call(method, path, body=None): + connection = http.client.HTTPConnection('127.0.0.1', host.port, timeout=30) + try: + connection.request(method, path, body, {'Content-Type': 'application/json'} if body is not None else {}) + answer = connection.getresponse() + return answer.status, answer.getheader('X-Pure-Error'), answer.read() + finally: + connection.close() + + rng = random.Random(5) + owners = ['김철수', 'bob', 'ann marie', 'a&b=c', '100%'] + accounts = {} + asked = 0 + try: + for aid in range(1, 201): + account = (aid % 7, rng.randrange(-500, 5000), rng.choice(owners)) + accounts[aid] = account + body = json.dumps({'aid': aid, 'bid': account[0], 'balance': account[1], 'owner': account[2]}).encode() + assert call('POST', '/inputs/open', body)[0] == 200 + for aid in rng.sample(range(1, 260), 60): + status, error, data = call('GET', f'/views/balance?aid={aid}') + assert (status, float(data)) == (200, accounts[aid][1]) if aid in accounts else (status, error) == (404, 'no_value') + asked += 1 + for low in [-1000, -1, 0, 0.5, 1000, 2500.25, 6000]: + assert float(call('GET', f'/views/richer?low={low}')[2]) == sum(1 for _, b, _ in accounts.values() if b > low) + asked += 1 + for name in ['세계 world', 'a&b=c', '100% sure', '😀']: + status, _, data = call('GET', f'/views/hello?name={urllib.parse.quote(name)}×=3') + assert (status, data.decode()) == (200, f'{name} x3') + asked += 1 + for who in owners: + data = call('GET', f'/views/owned?who={urllib.parse.quote(who)}')[2] + assert float(data) == sum(1 for _, _, o in accounts.values() if o == who) + asked += 1 + for b in range(7): + over = rng.choice([-1000, 0, 1500]) + rows = [(aid, balance, owner) for aid, (bid, balance, owner) in sorted(accounts.items()) if bid == b and balance > over] + total = sum(balance for bid, balance, _ in accounts.values() if bid == b) + page = json.loads(call('GET', f'/views/branch?b={b}&over={over}&from=2&count=5')[2]) + assert page['rows'] == len(rows) and page['from'] == min(2, len(rows)) + assert [(r['aid'], r['share'], r['owner']) for r in page['items']] == \ + [(aid, balance / total, owner) for aid, balance, owner in rows[2:7]] + asked += 1 + # Arguments missing, unknown, not of their type, or badly escaped. + for path in ['/views/balance', '/views/balance?aid=x', '/views/balance?aid=1&zz=2', '/views/balance?aid=-1', + '/views/hello?times=1&name=%zz', '/views/richer?low=1&low=2']: + assert call('GET', path)[:2] == (400, 'invalid'), path + assert call('GET', '/views/branch?b=1&from=x')[:2] == (400, 'query') + # A registered query takes arguments as a view does. + (work / 'top.aup').write_text('view top(k: u32) = each a in accounts where a.aid <= k {\n aid = a.aid\n}\n') + run(aup, 'query', source, work / 'top.aup', '-o', work / 'top.pmod') + assert call('PUT', '/queries/top', (work / 'top.pmod').read_bytes())[0] == 200 + assert [r['aid'] for r in json.loads(call('GET', '/queries/top?k=4')[2])['items']] == [1, 2, 3, 4] + assert call('GET', '/queries/top')[:2] == (400, 'invalid') + finally: + host.stop() + return {'accounts': len(accounts), 'asked': asked} + + def server_queries(aup, pure, work): """Queries as verified programs over a snapshot (PUT and GET /queries/NAME): aup query compiles a bank's declarations with views of its own; the node @@ -1766,6 +1867,7 @@ def main(): report['server_surface'] = server_surface(aup, pure, work) report['server_queries'] = server_queries(aup, pure, work) report['server_rows'] = server_rows(aup, pure, work) + report['server_arguments'] = server_arguments(aup, pure, work) report['server_answers'] = server_answers(aup, pure, work) print(json.dumps(report)) |
