north-scape/relayboard
Relayboard: accessible team boards with audited actions, dependency maps, and shared web/MCP use cases.
A remix template · 0 remixes · Remix north-scape/relayboard
tests/test_template.py
"""The lightweight local validator must at least reject malformed YAML."""
import importlib.util
from pathlib import Path
import pytest
import yaml
import shutil
spec = importlib.util.spec_from_file_location('check_template', Path(__file__).resolve().parents[1] / 'scripts/check_template.py')
template = importlib.util.module_from_spec(spec)
spec.loader.exec_module(template)
def test_manifest_description_with_colon_must_be_quoted():
with pytest.raises(yaml.YAMLError):
template.parse_manifest('version: 1\ndescription: Relayboard: accessible boards\n')
assert template.parse_manifest('version: 1\ndescription: "Relayboard: accessible boards"\n')['version'] == 1
def test_manifest_root_must_be_mapping():
with pytest.raises(ValueError, match='mapping'):
template.parse_manifest('- not\n- a mapping\n')
@pytest.fixture
def fresh_template(tmp_path):
root = Path(__file__).resolve().parents[1]
for name in ('.dodil', '.github'):
shutil.copytree(root / name, tmp_path / name)
for name in ('schema.sql', 'LICENSE'):
shutil.copy(root / name, tmp_path / name)
return tmp_path
def test_current_fresh_install_contract(fresh_template):
template.validate_template(fresh_template)
@pytest.mark.parametrize('before,after,message', [
('CREATE TABLE IF NOT EXISTS boards', 'CREATE TABLE IF NOT EXISTS lost_boards', 'missing table boards'),
(" checklist TEXT NOT NULL DEFAULT '[]',\n", '', 'missing columns'),
(' revision INTEGER NOT NULL DEFAULT 0,', ' revision INTEGER NOT NULL,', 'default to 0'),
("checklist TEXT NOT NULL DEFAULT '[]'", "checklist TEXT NOT NULL DEFAULT '{}'", 'empty JSON list'),
(' id TEXT PRIMARY KEY,', ' id TEXT,', 'primary key'),
])
def test_incomplete_fresh_schema_is_rejected(fresh_template, before, after, message):
path = fresh_template / 'schema.sql'
original = path.read_text()
assert before in original
path.write_text(original.replace(before, after))
with pytest.raises(ValueError, match=message):
template.validate_template(fresh_template)
def test_empty_schema_cannot_pass_vacuously(fresh_template):
(fresh_template / 'schema.sql').write_text('-- no tables\n')
with pytest.raises(ValueError, match='missing table boards'):
template.validate_template(fresh_template)
@pytest.mark.parametrize('before,after,message', [
('create_if_missing: true', 'create_if_missing: false', 'create the app'),
('public_invoke: true', 'public_invoke: false', 'OAuth discovery'),
('user_pool: "${{ dodil.repo }}-users"', 'user_pool: old-users', 'provisioned pool'),
('BUCKET: "${{ dodil.repo }}-data"', 'BUCKET: old-data', 'provisioned bucket'),
('"${{ dodil.org }}/${{ dodil.repo }}-users"', '"original-org/original-users"', 'OAuth issuer'),
('"${{ secrets.DODIL_SERVICE_ACCOUNT_ID }}"', '"hardcoded-client"', 'generated CI secret'),
('port: 8080', 'port: 8000', 'port/health'),
])
def test_broken_remix_deploy_binding_is_rejected(fresh_template, before, after, message):
path = fresh_template / '.dodil/deploy.yaml'
original = path.read_text()
assert before in original
path.write_text(original.replace(before, after))
with pytest.raises(ValueError, match=message):
template.validate_template(fresh_template)
@pytest.mark.parametrize('policy', ['suffix', 'reuse'])
def test_resource_conflicts_cannot_silently_rebind(fresh_template, policy):
path = fresh_template / '.dodil/remix.yaml'
path.write_text(path.read_text().replace(' schema: schema.sql', f' on_conflict: {policy}\n schema: schema.sql'))
with pytest.raises(ValueError, match='conflict policy'):
template.validate_template(fresh_template)
def test_missing_models_grant_is_rejected(fresh_template):
path = fresh_template / '.dodil/remix.yaml'
path.write_text(path.read_text().replace('role: ignite.model-user', 'role: ignite.viewer'))
with pytest.raises(ValueError, match='Models access'):
template.validate_template(fresh_template)
@pytest.mark.parametrize('before,after', [
(' revision INTEGER NOT NULL,', ' revision TEXT NOT NULL,'),
(' due_date DATE,', ' due_date TEXT,'),
(' updated_at TIMESTAMP', ' updated_at TEXT'),
])
def test_adapter_column_types_are_checked(fresh_template, before, after):
path = fresh_template / 'schema.sql'
original = path.read_text()
assert before in original
path.write_text(original.replace(before, after))
with pytest.raises(ValueError, match='expected type'):
template.validate_template(fresh_template)
def test_additional_tables_still_require_primary_keys(fresh_template):
path = fresh_template / 'schema.sql'
path.write_text(path.read_text() + '\nCREATE TABLE extra (id TEXT);\n')
with pytest.raises(ValueError, match='extra: table needs a primary key'):
template.validate_template(fresh_template)
def test_not_null_before_primary_key_is_valid(fresh_template):
path = fresh_template / 'schema.sql'
path.write_text(path.read_text().replace('id TEXT PRIMARY KEY', 'id TEXT NOT NULL PRIMARY KEY'))
template.validate_template(fresh_template)
def test_remix_readme_selects_and_verifies_org_before_pool_changes():
readme = (Path(__file__).resolve().parents[1] / 'README.md').read_text()
after = readme.split('### After the remix: sign-in step', 1)[1]
switch = after.index('dodil auth use-org <your-org> &&')
identity = after.index('dodil auth status &&')
pool = after.index('dodil appid pool get <repo>-users -o json')
connection = after.index('dodil appid connection list')
change = after.index('dodil appid connection add')
assert switch < identity < pool < connection < change
assert 'https://appid.dodil.io/<your-org>/<repo>-users' in after[:connection]
assert 'Stop if switching fails' in after[:connection]
assert 'including any suffix' in after[:connection]