mateclaw/mateclaw-server/src/test/java/vip/mate/trigger/TriggerServiceLifecycleTest.java
matevip 0ffc224623 chore(test): backfill mateclaw-server/src/test/ that earlier PRIVATE_ITEMS pattern accidentally excluded
The PRIVATE_ITEMS list contained the bare 'test' entry, which rsync
interprets as 'any directory named test at any depth' — so it caught
the root-level /test/ scratch directory (intended) AND every src/test/
under each module (not intended).

Pattern is already anchored to /test (root-only). This commit rsyncs
the accumulated src/test/ tree forward so opensource has the unit tests
that have been written / updated against existing src/main/ code since
the pattern regression. Going forward each per-commit sync will carry
src/test/ files along with the main change.
2026-05-12 17:38:08 +08:00

110 lines
4.7 KiB
Java

package vip.mate.trigger;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import vip.mate.MateClawApplication;
import vip.mate.trigger.model.TriggerEntity;
import vip.mate.trigger.repository.TriggerMapper;
import vip.mate.trigger.scheduler.TriggerScheduler;
import vip.mate.trigger.service.TriggerService;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* Covers the lamport / scheduler-sync invariants of {@link TriggerService}:
* pattern_version must bump on every cron expression / pattern type change
* and on every enable→disable transition; the scheduler must mirror the
* row's enabled state. The tests rely on the scheduler's package-private
* {@code isRegistered} accessor instead of waiting for an actual cron tick.
*/
@SpringBootTest(
classes = MateClawApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.NONE
)
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:trigger_lifecycle_${random.uuid};MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1",
"spring.ai.dashscope.api-key=test-key",
"spring.main.web-application-type=none"
})
class TriggerServiceLifecycleTest {
@Autowired private TriggerService triggerService;
@Autowired private TriggerMapper triggerMapper;
@Autowired private TriggerScheduler scheduler;
@Test
@DisplayName("create() persists a v1 trigger and registers it with the scheduler when enabled.")
void createRegistersEnabled() {
TriggerEntity t = newCronTrigger("hourly", "0 0 * * * *", true);
TriggerEntity saved = triggerService.create(t);
assertEquals(1L, saved.getPatternVersion());
assertTrue(scheduler.isRegistered(saved.getId()));
// Disabled trigger row persists but does not occupy a scheduled slot.
TriggerEntity disabled = triggerService.create(newCronTrigger("dormant", "0 0 1 * * *", false));
assertEquals(1L, disabled.getPatternVersion());
assertFalse(scheduler.isRegistered(disabled.getId()));
}
@Test
@DisplayName("update() bumps pattern_version when the cron expression changes.")
void updateBumpsLamportOnPatternChange() {
TriggerEntity created = triggerService.create(newCronTrigger("flex", "0 0 * * * *", true));
long firstVersion = created.getPatternVersion();
created.setPatternJson("{\"cron\":\"0 30 * * * *\"}");
TriggerEntity updated = triggerService.update(created);
assertEquals(firstVersion + 1, updated.getPatternVersion());
// No-op update does not bump the lamport.
TriggerEntity reloaded = triggerMapper.selectById(updated.getId());
TriggerEntity touched = triggerService.update(reloaded);
assertEquals(updated.getPatternVersion(), touched.getPatternVersion());
}
@Test
@DisplayName("update() flipping enabled toggles scheduler registration and bumps lamport.")
void enableTransitionTogglesSchedulerAndBumpsLamport() {
TriggerEntity created = triggerService.create(newCronTrigger("toggle", "0 0 * * * *", true));
long version = created.getPatternVersion();
created.setEnabled(false);
TriggerEntity disabled = triggerService.update(created);
assertEquals(version + 1, disabled.getPatternVersion());
assertFalse(scheduler.isRegistered(disabled.getId()));
disabled.setEnabled(true);
TriggerEntity reEnabled = triggerService.update(disabled);
assertEquals(version + 2, reEnabled.getPatternVersion());
assertTrue(scheduler.isRegistered(reEnabled.getId()));
}
@Test
@DisplayName("delete() removes both the row and the scheduler registration.")
void deleteUnregistersAndRemovesRow() {
TriggerEntity created = triggerService.create(newCronTrigger("ephemeral", "0 0 * * * *", true));
long id = created.getId();
triggerService.delete(id);
assertFalse(scheduler.isRegistered(id));
assertNull(triggerMapper.selectById(id));
}
private static TriggerEntity newCronTrigger(String name, String cron, boolean enabled) {
TriggerEntity t = new TriggerEntity();
t.setWorkspaceId(99L);
t.setName(name);
t.setPatternType("cron");
t.setPatternJson("{\"cron\":\"" + cron + "\"}");
t.setTargetType("workflow");
t.setTargetId(42L);
t.setEnabled(enabled);
return t;
}
}