# Bind Schedule Device Dispatch Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** When the app binds a schedule to one or more devices, immediately publish that schedule's detail payload to each bound device.
**Architecture:** Keep the change inside the existing app module flow. `AppController.addScheduleDevice` remains the entry point, persists the relation as it does today, then reuses the existing `IDeviceCommandService.sendScheduleBindCommand(...)` path to publish a `bindSchedule` command with a small payload assembled from current schedule-detail data. Shared payload shaping should stay private to `AppController` unless implementation pressure proves otherwise.
**Tech Stack:** Java 17, Spring Boot 3, existing app controller/service layer, MQTT command publishing through `IDeviceCommandService`, Maven
## Global Constraints
- Only touch the schedule-device binding flow in `water-modules/water-app`
- Reuse existing `IDeviceCommandService.sendScheduleBindCommand(...)`
- Do not modify MQTT publisher, ACK handling, or topic routing
- Published payload must contain `deviceNo` and `details`
- `details` must follow the current device-facing schedule detail structure used by `buildScheduleBindPayload`
- Binding uses synchronous failure semantics: command publish failure must fail the API call
- Keep changes ASCII unless the target file already uses non-ASCII
---
### Task 1: Add a focused regression test for schedule-device bind dispatch
**Files:**
- Create: `water-modules/water-app/src/test/java/org/dromara/app/controller/AppControllerTest.java`
- Modify: `water-modules/water-app/pom.xml`
**Interfaces:**
- Consumes: `AppController.addScheduleDevice(String body)`
- Produces: a regression test proving that binding a device triggers `deviceCommandService.sendScheduleBindCommand(deviceNo, payload)`
- [ ] **Step 1: Add the test dependency baseline for the app module**
```xml
org.springframework.boot
spring-boot-starter-test
test
```
- [ ] **Step 2: Write the failing controller regression test**
```java
package org.dromara.app.controller;
import org.dromara.app.domain.bo.AppSchedulingDeviceBo;
import org.dromara.app.domain.vo.AppDeviceVo;
import org.dromara.app.domain.vo.AppScheduleDetailVo;
import org.dromara.app.domain.vo.AppScheduleVo;
import org.dromara.app.service.IAppDeviceService;
import org.dromara.app.service.IAppScheduleDetailService;
import org.dromara.app.service.IAppScheduleService;
import org.dromara.app.service.IAppSchedulingDeviceService;
import org.dromara.app.service.IAppWateringLogService;
import org.dromara.app.service.IDeviceCommandService;
import org.dromara.common.core.domain.R;
import org.dromara.system.service.ISysUserService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class AppControllerTest {
@Mock private IAppDeviceService appDeviceService;
@Mock private IAppScheduleService appScheduleService;
@Mock private IAppScheduleDetailService appScheduleDetailService;
@Mock private org.dromara.app.service.impl.AppScheduleServiceImpl appScheduleServiceimpl;
@Mock private IAppSchedulingDeviceService appSchedulingDeviceService;
@Mock private ISysUserService userService;
@Mock private IAppWateringLogService appWateringLogService;
@Mock private IDeviceCommandService deviceCommandService;
@InjectMocks
private AppController controller;
@Test
void addScheduleDevice_dispatchesSchedulePayloadAfterBinding() {
AppScheduleVo schedule = new AppScheduleVo();
schedule.setId(10L);
schedule.setUserId(99L);
schedule.setName("Morning");
schedule.setStatus("1");
AppDeviceVo device = new AppDeviceVo();
device.setDeviceNo("D01");
device.setUserId(99L);
AppScheduleDetailVo detail = new AppScheduleDetailVo();
detail.setId(101L);
detail.setWeekday("1");
detail.setTimeData("[{\"startTime\":\"08:00\",\"durationMin\":15}]");
detail.setTriggerType("0");
detail.setStatus("1");
when(appScheduleService.queryById(10L)).thenReturn(schedule);
when(appDeviceService.queryById("D01")).thenReturn(device);
when(appScheduleDetailService.queryByScheduleIdByStatus(10L)).thenReturn(List.of(detail));
when(appSchedulingDeviceService.insertByBo(any(AppSchedulingDeviceBo.class))).thenReturn(true);
when(deviceCommandService.sendScheduleBindCommand(eq("D01"), anyMap())).thenReturn("cmd-1");
R result = controller.addScheduleDevice("{\"scheduleId\":10,\"deviceNos\":[\"D01\"]}");
assertThat(result.getCode()).isEqualTo(200);
ArgumentCaptor