Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a11beb-4eef-7fc1-94a7-597130a01ae9
141 lines
7.7 KiB
Python
141 lines
7.7 KiB
Python
import importlib.util
|
|
import io
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import Mock, patch
|
|
import urllib.error
|
|
import urllib.request
|
|
import zipfile
|
|
|
|
|
|
def module(name):
|
|
spec=importlib.util.spec_from_file_location(name,Path(__file__).parents[1]/'tools'/f'{name}.py')
|
|
m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m);return m
|
|
|
|
inv=module('inventory');verify=module('verify_inventory')
|
|
|
|
class InventoryTests(unittest.TestCase):
|
|
def test_scan_emits_reference_only_and_skips_sensitive_files(self):
|
|
with tempfile.TemporaryDirectory() as d:
|
|
p=Path(d)
|
|
subprocess.run(['git','init','-q',d],check=True)
|
|
(p/'values.yaml').write_text('image: https://user:do-not-emit@forgejo.example/coulomb/example:tag?token=also-private\n')
|
|
(p/'.env').write_text('SECRET=coulomb/private-secret\n')
|
|
(p/'link').symlink_to(p/'values.yaml')
|
|
subprocess.run(['git','-C',d,'add','.'],check=True)
|
|
subprocess.run(['git','-C',d,'-c','user.name=Test','-c','user.email=test@example.invalid','commit','-qm','fixture'],check=True)
|
|
result=inv.scan(p)
|
|
self.assertEqual([r['reference'] for r in result['references']],['coulomb/example'])
|
|
self.assertNotIn('do-not-emit',str(result));self.assertNotIn('also-private',str(result))
|
|
self.assertNotIn('private-secret',str(result))
|
|
self.assertEqual({e['reason'] for e in result['exclusions']},{'sensitive-or-encrypted','symlink-not-followed'})
|
|
self.assertEqual(result,inv.scan(p))
|
|
|
|
def test_paginated_server_read_does_not_stop_at_first_page(self):
|
|
original=inv.get
|
|
try:
|
|
inv.get=lambda url: [{'id':1}] if 'page=1' in url else [{'id':2}] if 'page=2' in url else []
|
|
self.assertEqual(inv.pages('/repos'),[{'id':1},{'id':2}])
|
|
inv.get=lambda url:[{'id':1}]
|
|
with self.assertRaises(RuntimeError):inv.pages('/repos')
|
|
finally:inv.get=original
|
|
|
|
def test_empty_partial_inventory_cannot_pass(self):
|
|
result=verify.check({'g1_complete':False,'local_repositories':[]},{'approved':False,'assignments':[]})
|
|
self.assertFalse(result['clean'])
|
|
|
|
def test_retained_org_and_stale_moved_path(self):
|
|
i={'g1_complete':True,'local_repositories':[{'name':'consumer','references':[{'reference':'coulomb/example','categories':['historical-or-planning']}]}]}
|
|
m={'approved':True,'assignments':[{'repository':'example','proposed_org':'coulomb'}]}
|
|
self.assertTrue(verify.check(i,m)['clean'])
|
|
m['assignments'][0]['proposed_org']='railiance'
|
|
self.assertFalse(verify.check(i,m)['clean'])
|
|
|
|
def test_authenticated_reads_use_get_and_are_scoped_to_forge(self):
|
|
response = Mock()
|
|
response.read.return_value = b'{"id": 42}'
|
|
opener = Mock()
|
|
opener.open.return_value.__enter__ = Mock(return_value=response)
|
|
opener.open.return_value.__exit__ = Mock(return_value=False)
|
|
with patch.object(inv.urllib.request, 'build_opener', return_value=opener):
|
|
self.assertEqual(inv.get(inv.BASE + '/api/v1/repos/coulomb/private', token='fixture-only'), {'id':42})
|
|
request = opener.open.call_args.args[0]
|
|
self.assertEqual(request.get_method(), 'GET')
|
|
self.assertEqual(request.get_header('Authorization'), 'token fixture-only')
|
|
for url in ['http://127.0.0.1:8000/repos/', 'https://other.invalid/repos/',
|
|
'http://forgejo.coulomb.social/repos/']:
|
|
with self.assertRaises(RuntimeError):
|
|
inv.get(url, token='fixture-only')
|
|
inv.get('http://127.0.0.1:8000/repos/')
|
|
self.assertIsNone(opener.open.call_args.args[0].get_header('Authorization'))
|
|
|
|
def test_authenticated_redirects_cannot_leak_token_or_downgrade(self):
|
|
req = urllib.request.Request(inv.BASE + '/start', headers={'Authorization':'token fixture-only'})
|
|
handler = inv.ScopedRedirects(True)
|
|
redirect = handler.redirect_request(req, None, 302, 'Found', {}, inv.BASE + '/next')
|
|
self.assertEqual(redirect.get_header('Authorization'), 'token fixture-only')
|
|
for url in ['https://other.invalid/next', 'http://forgejo.coulomb.social/next',
|
|
'https://user@forgejo.coulomb.social/next']:
|
|
with self.assertRaisesRegex(RuntimeError, '^Authenticated forge redirect refused$'):
|
|
handler.redirect_request(req, None, 302, 'Found', {}, url)
|
|
|
|
def test_transport_errors_do_not_emit_credential_or_raw_response(self):
|
|
for error, message in [
|
|
(urllib.error.URLError('fixture-only raw error'), 'Inventory read failed'),
|
|
(urllib.error.HTTPError(inv.BASE, 403, 'fixture-only raw response', {}, None), 'HTTP 403')]:
|
|
opener = Mock()
|
|
opener.open.side_effect = error
|
|
with patch.object(inv.urllib.request, 'build_opener', return_value=opener):
|
|
with self.assertRaises(RuntimeError) as caught:
|
|
inv.get(inv.BASE + '/api/v1/repos', token='fixture-only')
|
|
self.assertEqual(str(caught.exception), message)
|
|
|
|
def test_token_is_forwarded_to_all_metadata_reads_but_not_hub(self):
|
|
calls = []
|
|
def fake_get(url, *, token=None):
|
|
calls.append((url,token))
|
|
if url.endswith('/repos/'):
|
|
return []
|
|
if '/orgs/' in url and 'page=1' in url:
|
|
return [{'id':42, 'name':inv.SELF.name, 'full_name':'coulomb/'+inv.SELF.name, 'private':True}]
|
|
if '/orgs/' in url or '/packages/' in url:
|
|
return []
|
|
return {'version':'fixture'}
|
|
with tempfile.TemporaryDirectory() as root, patch.object(inv,'get',side_effect=fake_get):
|
|
result=inv.collect(Path(root),token='fixture-only')
|
|
self.assertFalse(result['g1_complete'])
|
|
self.assertEqual(result['forge']['authentication'],'authenticated-read')
|
|
self.assertTrue(result['server_repositories'][0]['private'])
|
|
for url,token in calls:
|
|
self.assertEqual(token, 'fixture-only' if url.startswith(inv.BASE) else None)
|
|
self.assertNotIn('fixture-only',json.dumps(result))
|
|
|
|
def test_missing_requested_credential_refuses_before_collection(self):
|
|
with tempfile.TemporaryDirectory() as root:
|
|
output=Path(root)/'snapshot.json'
|
|
env=dict(os.environ)
|
|
env.pop('ORGREF_TEST_MISSING_TOKEN',None)
|
|
result=subprocess.run([
|
|
'python3',str(Path(__file__).parents[1]/'tools/inventory.py'),
|
|
'--output',str(output),'--forge-token-env','ORGREF_TEST_MISSING_TOKEN'],
|
|
env=env,capture_output=True,text=True)
|
|
self.assertEqual(result.returncode,2)
|
|
self.assertFalse(output.exists())
|
|
|
|
def test_private_archive_uses_the_same_scoped_read_credential(self):
|
|
raw=io.BytesIO()
|
|
with zipfile.ZipFile(raw,'w') as archive:
|
|
archive.writestr('private/values.yaml','image: forgejo.example/coulomb/example:tag\n')
|
|
with patch.object(inv,'get',return_value={'commit':{'id':'abc'}}) as metadata, \
|
|
patch.object(inv,'read_bytes',return_value=raw.getvalue()) as archive_read:
|
|
result=inv.scan_archive({'name':'private','default_branch':'main'},token='fixture-only')
|
|
self.assertEqual(metadata.call_args.kwargs['token'],'fixture-only')
|
|
self.assertEqual(archive_read.call_args.kwargs,{'token':'fixture-only','limit':50_000_001})
|
|
self.assertEqual([r['reference'] for r in result['references']],['coulomb/example'])
|
|
self.assertNotIn('fixture-only',json.dumps(result))
|
|
|
|
if __name__=='__main__':unittest.main()
|