File size: 5,355 Bytes
177c40c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
"""
Constraint verification tests.

These tests verify that each constraint behaves correctly in isolation.
Use ConstraintVerifier to test individual constraints without running the full solver.
"""

import pytest
from solverforge_legacy.solver.test import ConstraintVerifier
from my_quickstart.domain import Resource, Task, Schedule
from my_quickstart.constraints import define_constraints


@pytest.fixture
def constraint_verifier():
    """Create a constraint verifier for testing."""
    return ConstraintVerifier.build(
        define_constraints,
        Schedule,
        Task,
    )


# =============================================================================
# TEST DATA FIXTURES
# =============================================================================

@pytest.fixture
def alice():
    return Resource(name="Alice", capacity=100, skills={"python", "sql"})


@pytest.fixture
def bob():
    return Resource(name="Bob", capacity=50, skills={"java"})


# =============================================================================
# HARD CONSTRAINT TESTS
# =============================================================================

class TestRequiredSkill:
    """Tests for the 'Required skill missing' constraint."""

    def test_no_penalty_when_skill_matches(self, constraint_verifier, alice):
        """Task with matching skill should not be penalized."""
        task = Task(id="1", name="Python Task", duration=30, required_skill="python", resource=alice)

        constraint_verifier.verify_that("Required skill missing") \
            .given(task) \
            .penalizes_by(0)

    def test_penalty_when_skill_missing(self, constraint_verifier, bob):
        """Task assigned to resource without required skill should be penalized."""
        task = Task(id="1", name="Python Task", duration=30, required_skill="python", resource=bob)

        constraint_verifier.verify_that("Required skill missing") \
            .given(task) \
            .penalizes_by(1)

    def test_no_penalty_when_no_skill_required(self, constraint_verifier, alice):
        """Task with no skill requirement should not be penalized."""
        task = Task(id="1", name="Any Task", duration=30, required_skill="", resource=alice)

        constraint_verifier.verify_that("Required skill missing") \
            .given(task) \
            .penalizes_by(0)


class TestResourceCapacity:
    """Tests for the 'Resource capacity exceeded' constraint."""

    def test_no_penalty_under_capacity(self, constraint_verifier, alice):
        """Tasks under capacity should not be penalized."""
        task1 = Task(id="1", name="Task 1", duration=30, resource=alice)
        task2 = Task(id="2", name="Task 2", duration=40, resource=alice)
        # Total: 70, Capacity: 100

        constraint_verifier.verify_that("Resource capacity exceeded") \
            .given(task1, task2) \
            .penalizes_by(0)

    def test_penalty_over_capacity(self, constraint_verifier, bob):
        """Tasks exceeding capacity should be penalized by the overflow amount."""
        task1 = Task(id="1", name="Task 1", duration=30, resource=bob)
        task2 = Task(id="2", name="Task 2", duration=40, resource=bob)
        # Total: 70, Capacity: 50, Overflow: 20

        constraint_verifier.verify_that("Resource capacity exceeded") \
            .given(task1, task2) \
            .penalizes_by(20)


# =============================================================================
# SOFT CONSTRAINT TESTS
# =============================================================================

class TestMinimizeDuration:
    """Tests for the 'Minimize total duration' constraint."""

    def test_penalizes_by_duration(self, constraint_verifier, alice):
        """Each assigned task should be penalized by its duration."""
        task = Task(id="1", name="Task", duration=45, resource=alice)

        constraint_verifier.verify_that("Minimize total duration") \
            .given(task) \
            .penalizes_by(45)

    def test_unassigned_not_penalized(self, constraint_verifier):
        """Unassigned tasks should not be penalized."""
        task = Task(id="1", name="Task", duration=45, resource=None)

        constraint_verifier.verify_that("Minimize total duration") \
            .given(task) \
            .penalizes_by(0)


# =============================================================================
# INTEGRATION TEST
# =============================================================================

class TestFullSolution:
    """Test the full constraint set on a complete solution."""

    def test_feasible_solution(self, constraint_verifier, alice, bob):
        """A feasible solution should have no hard constraint violations."""
        tasks = [
            Task(id="1", name="Python Task", duration=30, required_skill="python", resource=alice),
            Task(id="2", name="SQL Task", duration=20, required_skill="sql", resource=alice),
            Task(id="3", name="Java Task", duration=40, required_skill="java", resource=bob),
        ]

        # Verify no hard violations
        constraint_verifier.verify_that("Required skill missing") \
            .given(*tasks) \
            .penalizes_by(0)

        constraint_verifier.verify_that("Resource capacity exceeded") \
            .given(*tasks) \
            .penalizes_by(0)