P08-03: 配置の通常操作受入
This commit is contained in:
@@ -559,11 +559,20 @@ def action_gate(row, typed, root=HERE.parent):
|
||||
expected_reason = REASONS[label][0] if label in REASONS else {'missing_connection':'STAIR_LANDING_MISSING', 'refused_demolition':'STAIR_LANDING_IN_USE'}.get(label, '')
|
||||
require(row.get('expected', expected_reason) == expected_reason, 'required refusal reason changed')
|
||||
results = row.get('results', [])
|
||||
if kind == 'build' and results and results[0]['code'] != 'OK':
|
||||
reason = results[0]['details'].get('reason', results[0]['code'])
|
||||
require(not row['report']['can_build'], 'preview/command disagreement')
|
||||
require(reason in row['report']['error_codes'], 'preview reason differs from Runtime reason')
|
||||
if label in REASONS:
|
||||
code, message = REASONS[label]
|
||||
require(row['report']['error_codes'][0] == code and row['status'] == message, 'reason mismatch')
|
||||
require(not row['report']['can_build'] and not results, 'preview rejection bypassed')
|
||||
preserve(before, after, results)
|
||||
elif label == 'missing_connection' and not results:
|
||||
require(not row['report']['can_build'], 'preview/command disagreement')
|
||||
require('STAIR_LANDING_MISSING' in row['report']['error_codes'], 'preview reason differs from Runtime reason')
|
||||
require(row['report']['reasons'] and row['status'] == row['report']['reasons'][0]['message'], 'connection reason mismatch')
|
||||
preserve(before, after, [])
|
||||
elif kind == 'cancel':
|
||||
require(not results and row['status'] == '撤去を取り消しました。', 'cancel submitted command')
|
||||
preserve(before, after, results)
|
||||
@@ -584,7 +593,16 @@ def action_gate(row, typed, root=HERE.parent):
|
||||
else:
|
||||
require(result['code'] == 'OK', 'positive rejected')
|
||||
if kind == 'demolish':
|
||||
require(row['status'] == '%s | %s | %s' % (result['code'], result['details'].get('reason', ''), result['message']), 'displayed domain reason mismatch')
|
||||
tokens = (result['code'], result['details'].get('reason', ''), ' | ')
|
||||
require(not any(token and token in row['status'] for token in tokens), 'raw demolition token in status')
|
||||
if row['expected']:
|
||||
require(row['status'] == '撤去できません: '+result['message'], 'demolition refusal message mismatch')
|
||||
else:
|
||||
titles = {'gold':'所持金', 'mana':'マナ', 'materials':'資材', 'faith':'信仰'}
|
||||
refund = result['details']['refund']
|
||||
amounts = ['%s %d' % (titles.get(k, k), int(refund[k])) for k in sorted(refund) if int(refund[k]) != 0]
|
||||
expected_status = '撤去しました(返金: '+ '、'.join(amounts)+')' if amounts else '撤去しました(返金なし)'
|
||||
require(row['status'] == expected_status, 'demolition refund display mismatch')
|
||||
if not row['expected']:
|
||||
fid = row['instance_id']
|
||||
old = keyed(a['systems']['runtime']['construction']['facilities'], 'instance_id')
|
||||
@@ -606,7 +624,7 @@ def action_gate(row, typed, root=HERE.parent):
|
||||
require(row['report']['connection']['status'] == 'CONNECTED', 'positive connection absent')
|
||||
if label == 'missing_connection':
|
||||
require(row['status'] == '操作に失敗しました: '+result['message'], 'connection reason mismatch')
|
||||
require(row['report']['can_build'], 'connection refusal did not reach Runtime')
|
||||
require(not row['report']['can_build'], 'preview/command disagreement')
|
||||
|
||||
def check(project_root, evidence_dir):
|
||||
root, evidence = Path(project_root).resolve(), Path(evidence_dir).resolve()
|
||||
|
||||
@@ -98,6 +98,83 @@ class EvidenceTests(unittest.TestCase):
|
||||
def test_positive_whole_checker(self):
|
||||
self.assertEqual([], C.check(self.root, self.out))
|
||||
|
||||
def action_row(self, label):
|
||||
rows = C.load(self.out/'pair/writer-actions.json')['actions']
|
||||
return copy.deepcopy(next(row for row in rows if row['id'] == label))
|
||||
|
||||
def action_check(self, row, message=None, mutate_state=None):
|
||||
def typed(name):
|
||||
node = C.load(self.out/('pair/'+name+'.json'))
|
||||
if mutate_state and name == row['after']:
|
||||
mutate_state(node)
|
||||
return node
|
||||
if message:
|
||||
with self.assertRaisesRegex(ValueError, message):
|
||||
C.action_gate(row, typed, self.root)
|
||||
else:
|
||||
C.action_gate(row, typed, self.root)
|
||||
|
||||
def test_agreeing_preview_refusal(self):
|
||||
row = self.action_row('missing_connection')
|
||||
self.assertEqual(row['status'], row['report']['reasons'][0]['message'])
|
||||
self.action_check(row)
|
||||
|
||||
def test_preview_command_disagreement(self):
|
||||
row = self.action_row('missing_connection')
|
||||
row['report']['can_build'] = True
|
||||
row['results'] = [{'code':'VALIDATION_FAILED', 'details':{'reason':'STAIR_LANDING_MISSING'}, 'message':row['status']}]
|
||||
self.action_check(row, 'preview/command disagreement')
|
||||
|
||||
def test_preview_runtime_reason_difference(self):
|
||||
row = self.action_row('missing_connection')
|
||||
row['report']['error_codes'] = ['OVERLAP']
|
||||
row['results'] = [{'code':'VALIDATION_FAILED', 'details':{'reason':'STAIR_LANDING_MISSING'}, 'message':row['status']}]
|
||||
self.action_check(row, 'preview reason differs from Runtime reason')
|
||||
|
||||
def test_preview_partial_state_change(self):
|
||||
self.action_check(self.action_row('missing_connection'), 'cancel/preview partially applied',
|
||||
lambda node: C.typed_member(node, 'game_state', 'resources', 'materials').update(text='999'))
|
||||
|
||||
def test_readable_demolition_success(self):
|
||||
row = self.action_row('demolition')
|
||||
self.assertEqual('撤去しました(返金: 資材 2)', row['status'])
|
||||
self.action_check(row)
|
||||
|
||||
def test_demolition_missing_refund_amount(self):
|
||||
row = self.action_row('demolition'); row['status'] = '撤去しました(返金: 資材)'
|
||||
self.action_check(row, 'demolition refund display mismatch')
|
||||
|
||||
def test_demolition_wrong_refund_amount(self):
|
||||
row = self.action_row('demolition'); row['status'] = '撤去しました(返金: 資材 3)'
|
||||
self.action_check(row, 'demolition refund display mismatch')
|
||||
|
||||
def test_demolition_raw_enum_status(self):
|
||||
row = self.action_row('demolition'); row['status'] = 'OK | |'
|
||||
self.action_check(row, 'raw demolition token in status')
|
||||
|
||||
def test_readable_demolition_refusal(self):
|
||||
row = self.action_row('refused_demolition')
|
||||
self.assertEqual('STAIR_LANDING_IN_USE', row['results'][0]['details']['reason'])
|
||||
self.assertEqual('撤去できません: '+row['results'][0]['message'], row['status'])
|
||||
self.action_check(row)
|
||||
|
||||
def test_demolition_raw_reason_status(self):
|
||||
row = self.action_row('refused_demolition'); row['status'] += ' STAIR_LANDING_IN_USE'
|
||||
self.action_check(row, 'raw demolition token in status')
|
||||
|
||||
def test_demolition_altered_refusal_message(self):
|
||||
for status in ('撤去できません', '撤去できません: altered'):
|
||||
row = self.action_row('refused_demolition'); row['status'] = status
|
||||
self.action_check(row, 'demolition refusal message mismatch')
|
||||
|
||||
def test_demolition_raw_reason_mismatch(self):
|
||||
row = self.action_row('refused_demolition')
|
||||
row['results'][0]['details']['reason'] = 'OTHER'
|
||||
def mutate(node):
|
||||
items = C.typed_member(node, 'systems', 'runtime', 'command_queue', 'processed')['items']
|
||||
items[-1] = F.typed(row['results'][0])
|
||||
self.action_check(row, 'reason mismatch', mutate)
|
||||
|
||||
def test_verify_only_cli_uses_build_output_directory(self):
|
||||
output = io.StringIO()
|
||||
with patch.dict(os.environ, {'BUILD_OUTPUT_DIR':str(self.out.parent)}), contextlib.redirect_stdout(output):
|
||||
|
||||
Reference in New Issue
Block a user