Implement Open Source edition limits and feature restrictions

- Add 5 mutation types (paraphrase, noise, tone_shift, prompt_injection, custom)
- Cap mutations at 50 per test run
- Force sequential execution only
- Disable GitHub Actions integration (Cloud feature)
- Add upgrade prompts throughout CLI
- Update README with feature comparison
- Add limits.py module for centralized limit management
- Add cloud and limits CLI commands
- Update all documentation with Cloud upgrade messaging
This commit is contained in:
Entropix 2025-12-29 00:11:02 +08:00
parent 2016be238d
commit 7b75fc9530
47 changed files with 3560 additions and 1012 deletions

View file

@ -3,26 +3,27 @@ Tests for the mutation engine.
"""
import pytest
from entropix.mutations.types import MutationType, Mutation
from entropix.mutations.templates import MutationTemplates, MUTATION_TEMPLATES
from entropix.mutations.templates import MutationTemplates
from entropix.mutations.types import Mutation, MutationType
class TestMutationType:
"""Tests for MutationType enum."""
def test_mutation_type_values(self):
"""Test mutation type string values."""
assert MutationType.PARAPHRASE.value == "paraphrase"
assert MutationType.NOISE.value == "noise"
assert MutationType.TONE_SHIFT.value == "tone_shift"
assert MutationType.PROMPT_INJECTION.value == "prompt_injection"
def test_display_name(self):
"""Test display name generation."""
assert MutationType.PARAPHRASE.display_name == "Paraphrase"
assert MutationType.TONE_SHIFT.display_name == "Tone Shift"
assert MutationType.PROMPT_INJECTION.display_name == "Prompt Injection"
def test_default_weights(self):
"""Test default weights are assigned."""
assert MutationType.PARAPHRASE.default_weight == 1.0
@ -32,7 +33,7 @@ class TestMutationType:
class TestMutation:
"""Tests for Mutation dataclass."""
def test_mutation_creation(self):
"""Test creating a mutation."""
mutation = Mutation(
@ -41,11 +42,11 @@ class TestMutation:
type=MutationType.PARAPHRASE,
weight=1.0,
)
assert mutation.original == "Book a flight"
assert mutation.mutated == "I need to fly somewhere"
assert mutation.type == MutationType.PARAPHRASE
def test_mutation_id_generation(self):
"""Test unique ID generation."""
m1 = Mutation(
@ -58,36 +59,36 @@ class TestMutation:
mutated="Test 2",
type=MutationType.NOISE,
)
assert m1.id != m2.id
assert len(m1.id) == 12
def test_mutation_validity(self):
"""Test mutation validity checks."""
# Valid mutation
# Valid mutation (mutated must be different and <= 3x original length)
valid = Mutation(
original="Test",
mutated="Different text",
original="What is the weather today?",
mutated="Tell me about the weather",
type=MutationType.PARAPHRASE,
)
assert valid.is_valid()
# Invalid: same as original
invalid_same = Mutation(
original="Test",
mutated="Test",
original="Test prompt",
mutated="Test prompt",
type=MutationType.PARAPHRASE,
)
assert not invalid_same.is_valid()
# Invalid: empty mutated
invalid_empty = Mutation(
original="Test",
original="Test prompt",
mutated="",
type=MutationType.PARAPHRASE,
)
assert not invalid_empty.is_valid()
def test_mutation_serialization(self):
"""Test to_dict and from_dict."""
mutation = Mutation(
@ -96,10 +97,10 @@ class TestMutation:
type=MutationType.NOISE,
weight=0.8,
)
data = mutation.to_dict()
restored = Mutation.from_dict(data)
assert restored.original == mutation.original
assert restored.mutated == mutation.mutated
assert restored.type == mutation.type
@ -107,40 +108,36 @@ class TestMutation:
class TestMutationTemplates:
"""Tests for MutationTemplates."""
def test_all_types_have_templates(self):
"""Test that all mutation types have templates."""
templates = MutationTemplates()
for mutation_type in MutationType:
template = templates.get(mutation_type)
assert template is not None
assert "{prompt}" in template
def test_format_template(self):
"""Test formatting a template with a prompt."""
templates = MutationTemplates()
formatted = templates.format(
MutationType.PARAPHRASE,
"Book a flight to Paris"
)
formatted = templates.format(MutationType.PARAPHRASE, "Book a flight to Paris")
assert "Book a flight to Paris" in formatted
assert "{prompt}" not in formatted
def test_custom_template(self):
"""Test setting a custom template."""
templates = MutationTemplates()
custom = "Custom template for {prompt}"
templates.set_template(MutationType.NOISE, custom)
assert templates.get(MutationType.NOISE) == custom
def test_custom_template_requires_placeholder(self):
"""Test that custom templates must have {prompt} placeholder."""
templates = MutationTemplates()
with pytest.raises(ValueError):
templates.set_template(MutationType.NOISE, "No placeholder here")