T006: schema validation tests + fixtures
This commit is contained in:
parent
7381f1cdee
commit
1fa64de72b
8 changed files with 333 additions and 0 deletions
19
schemas/tests/fixtures/listing_bad_status.json
vendored
Normal file
19
schemas/tests/fixtures/listing_bad_status.json
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"schema_version": "m2.listing.v1",
|
||||
"listing_id": "lst_mm-pdf-report",
|
||||
"solution_id": "sol_mm-pdf-report",
|
||||
"solution_version": "1.0.0",
|
||||
"inventory_type": "solution",
|
||||
"name": "MM PDF Branded Report",
|
||||
"summary": "Generate Machine.Machine branded PDF reports and slide decks from Markdown",
|
||||
"category": "reporting",
|
||||
"price": {
|
||||
"amount": 500,
|
||||
"currency": "m2cr",
|
||||
"model": "fixed"
|
||||
},
|
||||
"seller": "sdjs-operator",
|
||||
"tenant_visibility": ["*"],
|
||||
"status": "archived",
|
||||
"install_ref": "lst_mm-pdf-report-v1.0.0"
|
||||
}
|
||||
18
schemas/tests/fixtures/listing_missing_tenant_visibility.json
vendored
Normal file
18
schemas/tests/fixtures/listing_missing_tenant_visibility.json
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"schema_version": "m2.listing.v1",
|
||||
"listing_id": "lst_mm-pdf-report",
|
||||
"solution_id": "sol_mm-pdf-report",
|
||||
"solution_version": "1.0.0",
|
||||
"inventory_type": "solution",
|
||||
"name": "MM PDF Branded Report",
|
||||
"summary": "Generate Machine.Machine branded PDF reports and slide decks from Markdown",
|
||||
"category": "reporting",
|
||||
"price": {
|
||||
"amount": 500,
|
||||
"currency": "m2cr",
|
||||
"model": "fixed"
|
||||
},
|
||||
"seller": "sdjs-operator",
|
||||
"status": "published",
|
||||
"install_ref": "lst_mm-pdf-report-v1.0.0"
|
||||
}
|
||||
34
schemas/tests/fixtures/solution_bad_price.json
vendored
Normal file
34
schemas/tests/fixtures/solution_bad_price.json
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"schema_version": "m2.solution.v1",
|
||||
"solution_id": "sol_mm-pdf-report",
|
||||
"name": "MM PDF Branded Report",
|
||||
"summary": "Generate Machine.Machine branded PDF reports and slide decks from Markdown",
|
||||
"intent": "Produce publish-ready branded PDF/slide deck output from Markdown content without manual design work",
|
||||
"deployment": {
|
||||
"recipe_ref": "recipes/mm-pdf-report/recipe.yaml",
|
||||
"entrypoint": "skills/mm-pdf/render.py",
|
||||
"verify_command": "python skills/mm-pdf/render.py --self-test"
|
||||
},
|
||||
"applicability": {
|
||||
"image_classes": ["desktop-agent"],
|
||||
"roles": ["operator", "analyst"],
|
||||
"tool_requirements": ["weasyprint"]
|
||||
},
|
||||
"tenant_scope": "m2-core",
|
||||
"evidence": [
|
||||
{
|
||||
"source": "session:2026-05-11-mm-pdf-branded-report"
|
||||
}
|
||||
],
|
||||
"content_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"price": {
|
||||
"amount": 0,
|
||||
"currency": "m2cr",
|
||||
"model": "fixed"
|
||||
},
|
||||
"seller": "sdjs-operator",
|
||||
"license": {
|
||||
"terms": "Single-operator install license; covers all machines under the buying operator_id.",
|
||||
"major_version_coverage": true
|
||||
}
|
||||
}
|
||||
30
schemas/tests/fixtures/solution_missing_evidence.json
vendored
Normal file
30
schemas/tests/fixtures/solution_missing_evidence.json
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"schema_version": "m2.solution.v1",
|
||||
"solution_id": "sol_mm-pdf-report",
|
||||
"name": "MM PDF Branded Report",
|
||||
"summary": "Generate Machine.Machine branded PDF reports and slide decks from Markdown",
|
||||
"intent": "Produce publish-ready branded PDF/slide deck output from Markdown content without manual design work",
|
||||
"deployment": {
|
||||
"recipe_ref": "recipes/mm-pdf-report/recipe.yaml",
|
||||
"entrypoint": "skills/mm-pdf/render.py",
|
||||
"verify_command": "python skills/mm-pdf/render.py --self-test"
|
||||
},
|
||||
"applicability": {
|
||||
"image_classes": ["desktop-agent"],
|
||||
"roles": ["operator", "analyst"],
|
||||
"tool_requirements": ["weasyprint"]
|
||||
},
|
||||
"tenant_scope": "m2-core",
|
||||
"evidence": [],
|
||||
"content_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"price": {
|
||||
"amount": 500,
|
||||
"currency": "m2cr",
|
||||
"model": "fixed"
|
||||
},
|
||||
"seller": "sdjs-operator",
|
||||
"license": {
|
||||
"terms": "Single-operator install license; covers all machines under the buying operator_id.",
|
||||
"major_version_coverage": true
|
||||
}
|
||||
}
|
||||
34
schemas/tests/fixtures/solution_tenant_violation.json
vendored
Normal file
34
schemas/tests/fixtures/solution_tenant_violation.json
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"schema_version": "m2.solution.v1",
|
||||
"solution_id": "sol_mm-pdf-report",
|
||||
"name": "MM PDF Branded Report",
|
||||
"summary": "Generate Machine.Machine branded PDF reports and slide decks from Markdown",
|
||||
"intent": "Produce publish-ready branded PDF/slide deck output from Markdown content without manual design work",
|
||||
"deployment": {
|
||||
"recipe_ref": "recipes/mm-pdf-report/recipe.yaml",
|
||||
"entrypoint": "skills/mm-pdf/render.py",
|
||||
"verify_command": "python skills/mm-pdf/render.py --self-test"
|
||||
},
|
||||
"applicability": {
|
||||
"image_classes": ["desktop-agent"],
|
||||
"roles": ["operator", "analyst"],
|
||||
"tool_requirements": ["weasyprint"]
|
||||
},
|
||||
"tenant_scope": "sdjs",
|
||||
"evidence": [
|
||||
{
|
||||
"source": "session:2026-05-11-mm-pdf-branded-report"
|
||||
}
|
||||
],
|
||||
"content_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"price": {
|
||||
"amount": 500,
|
||||
"currency": "m2cr",
|
||||
"model": "fixed"
|
||||
},
|
||||
"seller": "sdjs-operator",
|
||||
"license": {
|
||||
"terms": "Single-operator install license; covers all machines under the buying operator_id.",
|
||||
"major_version_coverage": true
|
||||
}
|
||||
}
|
||||
27
schemas/tests/fixtures/valid_listing.json
vendored
Normal file
27
schemas/tests/fixtures/valid_listing.json
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"schema_version": "m2.listing.v1",
|
||||
"listing_id": "lst_mm-pdf-report",
|
||||
"solution_id": "sol_mm-pdf-report",
|
||||
"solution_version": "1.0.0",
|
||||
"inventory_type": "solution",
|
||||
"name": "MM PDF Branded Report",
|
||||
"summary": "Generate Machine.Machine branded PDF reports and slide decks from Markdown",
|
||||
"category": "reporting",
|
||||
"keywords": ["pdf", "report", "branded", "slides"],
|
||||
"icon": "https://git.machinemachine.ai/m2/market-registry/assets/mm-pdf-report/icon.png",
|
||||
"price": {
|
||||
"amount": 500,
|
||||
"currency": "m2cr",
|
||||
"model": "fixed"
|
||||
},
|
||||
"seller": "sdjs-operator",
|
||||
"evidence_summary": "Generated branded PDF report for SDJS quarterly summary; operator confirmed styling matched brand deck.",
|
||||
"tenant_visibility": ["*"],
|
||||
"stats": {
|
||||
"installs": 0,
|
||||
"proposals_shown": 0,
|
||||
"proposals_accepted": 0
|
||||
},
|
||||
"status": "published",
|
||||
"install_ref": "lst_mm-pdf-report-v1.0.0"
|
||||
}
|
||||
69
schemas/tests/fixtures/valid_solution.json
vendored
Normal file
69
schemas/tests/fixtures/valid_solution.json
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"schema_version": "m2.solution.v1",
|
||||
"solution_id": "sol_mm-pdf-report",
|
||||
"name": "MM PDF Branded Report",
|
||||
"summary": "Generate Machine.Machine branded PDF reports and slide decks from Markdown",
|
||||
"description": "Renders styled dark-navy/cyan PDF reports and Marp slide decks from Markdown source, using WeasyPrint or Marp depending on target.",
|
||||
"intent": "Produce publish-ready branded PDF/slide deck output from Markdown content without manual design work",
|
||||
"behavior": {
|
||||
"skill_ref": "skills/mm-pdf/SKILL.md"
|
||||
},
|
||||
"tools": [
|
||||
{
|
||||
"name": "weasyprint",
|
||||
"type": "cli"
|
||||
},
|
||||
{
|
||||
"name": "marp",
|
||||
"type": "container",
|
||||
"container": "nasr-m2o"
|
||||
}
|
||||
],
|
||||
"runtime": {
|
||||
"surfaces": ["desktop", "hermes"]
|
||||
},
|
||||
"memory_schema": {
|
||||
"partitions": ["mm-pdf:renders"]
|
||||
},
|
||||
"permissions": [
|
||||
{
|
||||
"scope": "filesystem:write",
|
||||
"path": "/tmp/mm-pdf-output"
|
||||
}
|
||||
],
|
||||
"deployment": {
|
||||
"recipe_ref": "recipes/mm-pdf-report/recipe.yaml",
|
||||
"entrypoint": "skills/mm-pdf/render.py",
|
||||
"verify_command": "python skills/mm-pdf/render.py --self-test"
|
||||
},
|
||||
"applicability": {
|
||||
"image_classes": ["desktop-agent"],
|
||||
"roles": ["operator", "analyst"],
|
||||
"tool_requirements": ["weasyprint"]
|
||||
},
|
||||
"tenant_scope": "m2-core",
|
||||
"evidence": [
|
||||
{
|
||||
"source": "session:2026-05-11-mm-pdf-branded-report",
|
||||
"excerpt": "Generated branded PDF report for SDJS quarterly summary; operator confirmed styling matched brand deck.",
|
||||
"machine": "nasr-m2o",
|
||||
"session": "sess_8f21c",
|
||||
"tokens": 4200,
|
||||
"wall_time": 38.4
|
||||
}
|
||||
],
|
||||
"content_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"price": {
|
||||
"amount": 500,
|
||||
"currency": "m2cr",
|
||||
"model": "fixed"
|
||||
},
|
||||
"seller": "sdjs-operator",
|
||||
"license": {
|
||||
"terms": "Single-operator install license; covers all machines under the buying operator_id.",
|
||||
"major_version_coverage": true
|
||||
},
|
||||
"revenue_split": {
|
||||
"platform_pct": 10
|
||||
}
|
||||
}
|
||||
102
schemas/tests/test_schemas.py
Normal file
102
schemas/tests/test_schemas.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
"""Schema validation tests for schemas/solution.schema.json and schemas/listing.schema.json.
|
||||
|
||||
Frozen v1 schemas (specs/001-market-first-wedge/data-model.md) — do not modify.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from jsonschema import Draft202012Validator
|
||||
|
||||
SCHEMAS_DIR = Path(__file__).resolve().parent.parent
|
||||
FIXTURES_DIR = Path(__file__).resolve().parent / "fixtures"
|
||||
|
||||
|
||||
def load(path):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
SOLUTION_SCHEMA = load(SCHEMAS_DIR / "solution.schema.json")
|
||||
LISTING_SCHEMA = load(SCHEMAS_DIR / "listing.schema.json")
|
||||
|
||||
solution_validator = Draft202012Validator(SOLUTION_SCHEMA)
|
||||
listing_validator = Draft202012Validator(LISTING_SCHEMA)
|
||||
|
||||
|
||||
def fixture(name):
|
||||
return load(FIXTURES_DIR / name)
|
||||
|
||||
|
||||
def errors(validator, instance):
|
||||
return list(validator.iter_errors(instance))
|
||||
|
||||
|
||||
# --- valid fixtures ---------------------------------------------------------
|
||||
|
||||
def test_valid_solution_passes():
|
||||
errs = errors(solution_validator, fixture("valid_solution.json"))
|
||||
assert errs == [], [e.message for e in errs]
|
||||
|
||||
|
||||
def test_valid_listing_passes():
|
||||
errs = errors(listing_validator, fixture("valid_listing.json"))
|
||||
assert errs == [], [e.message for e in errs]
|
||||
|
||||
|
||||
# --- invalid solution fixtures ----------------------------------------------
|
||||
|
||||
def test_solution_missing_evidence_fails_on_evidence_minitems():
|
||||
errs = errors(solution_validator, fixture("solution_missing_evidence.json"))
|
||||
assert errs, "expected validation errors"
|
||||
assert any(
|
||||
e.validator == "minItems" and list(e.path) == ["evidence"]
|
||||
for e in errs
|
||||
), [(e.validator, list(e.path), e.message) for e in errs]
|
||||
|
||||
|
||||
def test_solution_tenant_violation_fails_on_else_branch():
|
||||
errs = errors(solution_validator, fixture("solution_tenant_violation.json"))
|
||||
assert errs, "expected validation errors"
|
||||
# tenant_scope != "m2-core" routes through the top-level if/else; the
|
||||
# else branch requires owner_initiated + scrub_status, both absent here.
|
||||
# jsonschema surfaces these as top-level "required" errors (path == []).
|
||||
missing_required_props = {
|
||||
prop
|
||||
for e in errs
|
||||
if e.validator == "required" and list(e.path) == []
|
||||
for prop in ("owner_initiated", "scrub_status")
|
||||
if f"'{prop}'" in e.message
|
||||
}
|
||||
assert missing_required_props == {"owner_initiated", "scrub_status"}, [
|
||||
(e.validator, list(e.path), e.message) for e in errs
|
||||
]
|
||||
|
||||
|
||||
def test_solution_bad_price_fails_on_price_amount_minimum():
|
||||
errs = errors(solution_validator, fixture("solution_bad_price.json"))
|
||||
assert errs, "expected validation errors"
|
||||
assert any(
|
||||
e.validator == "minimum" and list(e.path) == ["price", "amount"]
|
||||
for e in errs
|
||||
), [(e.validator, list(e.path), e.message) for e in errs]
|
||||
|
||||
|
||||
# --- invalid listing fixtures ------------------------------------------------
|
||||
|
||||
def test_listing_bad_status_fails_on_status_enum():
|
||||
errs = errors(listing_validator, fixture("listing_bad_status.json"))
|
||||
assert errs, "expected validation errors"
|
||||
assert any(
|
||||
e.validator == "enum" and list(e.path) == ["status"]
|
||||
for e in errs
|
||||
), [(e.validator, list(e.path), e.message) for e in errs]
|
||||
|
||||
|
||||
def test_listing_missing_tenant_visibility_fails_on_required():
|
||||
errs = errors(listing_validator, fixture("listing_missing_tenant_visibility.json"))
|
||||
assert errs, "expected validation errors"
|
||||
assert any(
|
||||
e.validator == "required" and "tenant_visibility" in e.message and list(e.path) == []
|
||||
for e in errs
|
||||
), [(e.validator, list(e.path), e.message) for e in errs]
|
||||
Loading…
Reference in a new issue