设备模块单元测试

This commit is contained in:
ayan 2022-11-02 17:46:12 +08:00
parent 8691833c97
commit d96c9a54e2
15 changed files with 1012 additions and 65 deletions

View File

@ -0,0 +1,45 @@
#!/bin/bash
#set -x
#******************************************************************************
# @file : entrypoint.sh
# @author : wangyubin
# @date : 2018-08- 1 10:18:43
#
# @brief : entry point for manage service start order
# history : init
#******************************************************************************
: ${SLEEP_SECOND:=2}
wait_for() {
echo Waiting for $1 to listen on $2...
while ! nc -z $1 $2; do echo waiting...; sleep $SLEEP_SECOND; done
}
declare DEPENDS
declare CMD
while getopts "d:c:" arg
do
case $arg in
d)
DEPENDS=$OPTARG
;;
c)
CMD=$OPTARG
;;
?)
echo "unkonw argument"
exit 1
;;
esac
done
for var in ${DEPENDS//,/ }
do
host=${var%:*}
port=${var#*:}
wait_for $host $port
done
eval $CMD

View File

@ -4,12 +4,14 @@ import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import lombok.Setter;
import org.jetlinks.core.metadata.DataType;
import org.jetlinks.core.metadata.types.StringType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
@ -23,17 +25,33 @@ public class MeterRegistryManager {
private Map<String, MeterRegistry> meterRegistryMap = new ConcurrentHashMap<>();
private List<MeterRegistrySupplier> suppliers;
private final List<MeterRegistrySupplier> suppliers;
private MeterRegistry createMeterRegistry(String metric, String... tagKeys) {
return new CompositeMeterRegistry(Clock.SYSTEM,
suppliers.stream()
.map(supplier -> supplier.getMeterRegistry(metric, tagKeys))
public MeterRegistryManager(@Autowired(required = false) List<MeterRegistrySupplier> suppliers) {
this.suppliers = suppliers == null ? new ArrayList<>() : suppliers;
}
private MeterRegistry createMeterRegistry(String metric, Map<String, DataType> tagDefine) {
Map<String, DataType> tags = new HashMap<>(tagDefine);
MeterRegistrySettings settings = tags::put;
return new CompositeMeterRegistry(Clock.SYSTEM, suppliers
.stream()
.map(supplier -> supplier.getMeterRegistry(metric))
.collect(Collectors.toList()));
}
public MeterRegistry getMeterRegister(String metric, String... tagKeys) {
return meterRegistryMap.computeIfAbsent(metric, _metric -> createMeterRegistry(_metric, tagKeys));
return meterRegistryMap.computeIfAbsent(metric, _metric -> {
if (tagKeys.length == 0) {
return createMeterRegistry(metric, Collections.emptyMap());
}
return createMeterRegistry(metric, Arrays
.stream(tagKeys)
.collect(Collectors.toMap(Function.identity(), key -> StringType.GLOBAL)));
});
}
}

View File

@ -29,58 +29,58 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnBean(ProtocolSupports.class)
public class DeviceClusterConfiguration {
@Bean
public ClusterDeviceRegistry deviceRegistry(ProtocolSupports supports,
ClusterManager manager,
ConfigStorageManager storageManager,
DeviceOperationBroker handler) {
return new ClusterDeviceRegistry(supports,
storageManager,
manager,
handler,
CaffeinatedGuava.build(Caffeine.newBuilder()));
}
@Bean
@ConditionalOnBean(ClusterDeviceRegistry.class)
public BeanPostProcessor interceptorRegister(ClusterDeviceRegistry registry) {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DeviceMessageSenderInterceptor) {
registry.addInterceptor(((DeviceMessageSenderInterceptor) bean));
}
if (bean instanceof DeviceStateChecker) {
registry.addStateChecker(((DeviceStateChecker) bean));
}
return bean;
}
};
}
@Bean(initMethod = "init", destroyMethod = "shutdown")
@ConditionalOnBean(RpcManager.class)
public PersistenceDeviceSessionManager deviceSessionManager(RpcManager rpcManager) {
return new PersistenceDeviceSessionManager(rpcManager);
}
@ConditionalOnBean(DecodedClientMessageHandler.class)
@Bean
public ClusterSendToDeviceMessageHandler defaultSendToDeviceMessageHandler(DeviceSessionManager sessionManager,
DeviceRegistry registry,
MessageHandler messageHandler,
DecodedClientMessageHandler clientMessageHandler) {
return new ClusterSendToDeviceMessageHandler(sessionManager, messageHandler, registry, clientMessageHandler);
}
@Bean
public RpcDeviceOperationBroker rpcDeviceOperationBroker(RpcManager rpcManager,
DeviceSessionManager sessionManager) {
return new RpcDeviceOperationBroker(rpcManager, sessionManager);
}
// @Bean
// public ClusterDeviceRegistry deviceRegistry(ProtocolSupports supports,
// ClusterManager manager,
// ConfigStorageManager storageManager,
// DeviceOperationBroker handler) {
//
// return new ClusterDeviceRegistry(supports,
// storageManager,
// manager,
// handler,
// CaffeinatedGuava.build(Caffeine.newBuilder()));
// }
//
//
// @Bean
// @ConditionalOnBean(ClusterDeviceRegistry.class)
// public BeanPostProcessor interceptorRegister(ClusterDeviceRegistry registry) {
// return new BeanPostProcessor() {
// @Override
// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// if (bean instanceof DeviceMessageSenderInterceptor) {
// registry.addInterceptor(((DeviceMessageSenderInterceptor) bean));
// }
// if (bean instanceof DeviceStateChecker) {
// registry.addStateChecker(((DeviceStateChecker) bean));
// }
// return bean;
// }
// };
// }
//
// @Bean(initMethod = "init", destroyMethod = "shutdown")
// @ConditionalOnBean(RpcManager.class)
// public PersistenceDeviceSessionManager deviceSessionManager(RpcManager rpcManager) {
//
// return new PersistenceDeviceSessionManager(rpcManager);
// }
//
// @ConditionalOnBean(DecodedClientMessageHandler.class)
// @Bean
// public ClusterSendToDeviceMessageHandler defaultSendToDeviceMessageHandler(DeviceSessionManager sessionManager,
// DeviceRegistry registry,
// MessageHandler messageHandler,
// DecodedClientMessageHandler clientMessageHandler) {
// return new ClusterSendToDeviceMessageHandler(sessionManager, messageHandler, registry, clientMessageHandler);
// }
//
// @Bean
// public RpcDeviceOperationBroker rpcDeviceOperationBroker(RpcManager rpcManager,
// DeviceSessionManager sessionManager) {
// return new RpcDeviceOperationBroker(rpcManager, sessionManager);
// }
}

View File

@ -7,6 +7,7 @@ import org.jetlinks.community.elastic.search.service.ElasticSearchService;
import org.jetlinks.community.elastic.search.things.ElasticSearchColumnModeStrategy;
import org.jetlinks.community.elastic.search.things.ElasticSearchRowModeStrategy;
import org.jetlinks.community.things.data.ThingsDataRepositoryStrategy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -16,6 +17,7 @@ import org.springframework.context.annotation.Configuration;
public class ElasticSearchThingDataConfiguration {
@Bean
@ConditionalOnBean(ElasticSearchService.class)
public ElasticSearchColumnModeStrategy elasticSearchColumnModThingDataPolicy(
ThingsRegistry registry,
ElasticSearchService searchService,
@ -26,6 +28,7 @@ public class ElasticSearchThingDataConfiguration {
}
@Bean
@ConditionalOnBean(ElasticSearchService.class)
public ElasticSearchRowModeStrategy elasticSearchRowModThingDataPolicy(
ThingsRegistry registry,
ElasticSearchService searchService,

View File

@ -3,6 +3,7 @@ package org.jetlinks.community.elastic.search.index.strategies;
import org.hswebframework.utils.time.DateFormatter;
import org.jetlinks.community.elastic.search.index.ElasticSearchIndexProperties;
import org.jetlinks.community.elastic.search.service.reactive.ReactiveElasticsearchClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
@ -15,6 +16,7 @@ import java.util.Date;
* @since 1.0
*/
@Component
@ConditionalOnBean(ReactiveElasticsearchClient.class)
public class TimeByDayElasticSearchIndexStrategy extends TemplateElasticSearchIndexStrategy {
public TimeByDayElasticSearchIndexStrategy(ReactiveElasticsearchClient client, ElasticSearchIndexProperties properties) {

View File

@ -3,6 +3,7 @@ package org.jetlinks.community.gateway.external.socket;
import org.hswebframework.web.authorization.ReactiveAuthenticationManager;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.jetlinks.community.gateway.external.MessagingManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -16,10 +17,10 @@ import java.util.HashMap;
import java.util.Map;
@Configuration
//@ConditionalOnBean({
// ReactiveAuthenticationManager.class,
// UserTokenManager.class
//})
@ConditionalOnBean({
ReactiveAuthenticationManager.class,
UserTokenManager.class
})
public class WebSocketMessagingHandlerConfiguration {

View File

@ -8,6 +8,7 @@ import org.jetlinks.core.event.Subscription;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -30,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
*/
@Component
@Slf4j
@ConditionalOnBean(NetworkConfigManager.class)
public class DefaultNetworkManager implements NetworkManager, BeanPostProcessor, CommandLineRunner {
private final NetworkConfigManager configManager;

View File

@ -6,6 +6,7 @@ import org.jetlinks.community.elastic.search.index.ElasticSearchIndexManager;
import org.jetlinks.community.rule.engine.event.handler.RuleEngineLoggerIndexProvider;
import org.jetlinks.core.metadata.types.DateTimeType;
import org.jetlinks.core.metadata.types.StringType;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@ -16,6 +17,7 @@ import org.springframework.stereotype.Component;
@Component
@Order(1)
@Slf4j
@ConditionalOnBean(DefaultElasticSearchIndexMetadata.class)
public class RuleEngineLogIndexInitialize {
public RuleEngineLogIndexInitialize(ElasticSearchIndexManager indexManager) {

View File

@ -2,11 +2,13 @@ package org.jetlinks.community.rule.engine.event.handler;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.community.elastic.search.service.ElasticSearchService;
import org.jetlinks.community.elastic.search.service.reactive.ReactiveElasticsearchClient;
import org.jetlinks.community.gateway.annotation.Subscribe;
import org.jetlinks.community.rule.engine.entity.RuleEngineExecuteEventInfo;
import org.jetlinks.core.event.TopicPayload;
import org.jetlinks.rule.engine.defaults.LogEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@ -14,6 +16,7 @@ import reactor.core.publisher.Mono;
@Component
@Slf4j
@Order(3)
@ConditionalOnBean(ElasticSearchService.class)
public class RuleLogHandler {
@Autowired

View File

@ -0,0 +1,76 @@
package org.jetlinks.community.timeseries;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.jetlinks.community.timeseries.query.AggregationData;
import org.jetlinks.community.timeseries.query.AggregationQueryParam;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
public class NoneTimeSeriesManager implements TimeSeriesManager {
public static final NoneTimeSeriesService NONE = new NoneTimeSeriesService();
@Override
public TimeSeriesService getService(TimeSeriesMetric metric) {
return NONE;
}
@Override
public TimeSeriesService getServices(TimeSeriesMetric... metric) {
return NONE;
}
@Override
public TimeSeriesService getServices(String... metric) {
return NONE;
}
@Override
public TimeSeriesService getService(String metric) {
return NONE;
}
@Override
public Mono<Void> registerMetadata(TimeSeriesMetadata metadata) {
return Mono.empty();
}
static class NoneTimeSeriesService implements TimeSeriesService {
@Override
public Flux<TimeSeriesData> query(QueryParam queryParam) {
return Flux.empty();
}
@Override
public Flux<TimeSeriesData> multiQuery(Collection<QueryParam> query) {
return Flux.empty();
}
@Override
public Mono<Integer> count(QueryParam queryParam) {
return Mono.empty();
}
@Override
public Flux<AggregationData> aggregation(AggregationQueryParam queryParam) {
return Flux.empty();
}
@Override
public Mono<Void> commit(Publisher<TimeSeriesData> data) {
return Mono.empty();
}
@Override
public Mono<Void> commit(TimeSeriesData data) {
return Mono.empty();
}
@Override
public Mono<Void> save(Publisher<TimeSeriesData> data) {
return Mono.empty();
}
}
}

View File

@ -0,0 +1,18 @@
package org.jetlinks.community.timeseries;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Flux;
import java.time.Duration;
@Configuration(proxyBeanMethods = false)
public class TimeSeriesManagerConfiguration {
@ConditionalOnMissingBean(TimeSeriesManager.class)
@Bean
public NoneTimeSeriesManager timeSeriesManager() {
return new NoneTimeSeriesManager();
}
}

View File

@ -99,6 +99,13 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetlinks.community</groupId>
<artifactId>test-component</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,73 @@
package org.jetlinks.community.device;
import org.hswebframework.web.authorization.token.DefaultUserTokenManager;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.hswebframework.web.starter.jackson.CustomCodecsAutoConfiguration;
import org.jetlinks.community.configure.cluster.ClusterConfiguration;
import org.jetlinks.community.configure.device.DeviceClusterConfiguration;
import org.jetlinks.community.elastic.search.configuration.ElasticSearchConfiguration;
import org.jetlinks.core.ProtocolSupports;
import org.jetlinks.core.device.DeviceRegistry;
import org.jetlinks.core.device.StandaloneDeviceMessageBroker;
import org.jetlinks.core.device.session.DeviceSessionManager;
import org.jetlinks.core.server.MessageHandler;
import org.jetlinks.supports.device.session.LocalDeviceSessionManager;
import org.jetlinks.supports.official.JetLinksDeviceMetadataCodec;
import org.jetlinks.supports.test.InMemoryDeviceRegistry;
import org.jetlinks.supports.test.MockProtocolSupport;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ImportAutoConfiguration({
CodecsAutoConfiguration.class,
JacksonAutoConfiguration.class,
CustomCodecsAutoConfiguration.class,
ClusterConfiguration.class,
DeviceClusterConfiguration.class
})
public class DeviceTestConfiguration {
@Bean
public ProtocolSupports mockProtocolSupport(){
return new MockProtocolSupport();
}
@Bean
public UserTokenManager userTokenManager(){
return new DefaultUserTokenManager();
}
@Bean
public DeviceRegistry deviceRegistry() {
return new InMemoryDeviceRegistry();
}
@Bean
public MessageHandler messageHandler() {
return new StandaloneDeviceMessageBroker();
}
@Bean
public DeviceSessionManager deviceSessionManager() {
return LocalDeviceSessionManager.create();
}
@Bean
public ElasticSearchConfiguration searchConfiguration() {
return new ElasticSearchConfiguration();
}
@Bean
public JetLinksDeviceMetadataCodec jetLinksDeviceMetadataCodec(){
return new JetLinksDeviceMetadataCodec();
}
}

View File

@ -0,0 +1,695 @@
package org.jetlinks.community.device.web;
import com.alibaba.fastjson.JSON;
import lombok.SneakyThrows;
import org.jetlinks.community.test.spring.TestJetLinksController;
import org.jetlinks.core.metadata.SimplePropertyMetadata;
import org.jetlinks.core.metadata.types.FloatType;
import org.jetlinks.community.PropertyMetadataConstants;
import org.jetlinks.community.PropertyMetric;
import org.jetlinks.community.device.entity.DeviceInstanceEntity;
import org.jetlinks.community.device.entity.DeviceProductEntity;
import org.jetlinks.community.device.entity.DeviceTagEntity;
import org.jetlinks.community.device.service.LocalDeviceInstanceService;
import org.jetlinks.community.device.service.LocalDeviceProductService;
import org.jetlinks.community.device.service.data.DeviceDataService;
import org.jetlinks.community.device.web.request.AggRequest;
import org.jetlinks.community.relation.entity.RelationEntity;
import org.jetlinks.community.relation.service.RelatedObjectInfo;
import org.jetlinks.community.relation.service.RelationService;
import org.jetlinks.community.relation.service.request.SaveRelationRequest;
import org.jetlinks.community.timeseries.query.Aggregation;
import org.jetlinks.supports.official.JetLinksDeviceMetadata;
import org.jetlinks.supports.official.JetLinksDeviceMetadataCodec;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import reactor.test.StepVerifier;
import java.util.*;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@WebFluxTest(value = DeviceInstanceController.class, properties = {
"spring.reactor.debug-agent.enabled=true"
})
class DeviceInstanceControllerTest extends TestJetLinksController {
@Autowired
@SuppressWarnings("all")
private LocalDeviceInstanceService deviceService;
@Autowired
@SuppressWarnings("all")
private LocalDeviceProductService productService;
private String deviceId;
private String productId;
private String metadata;
@BeforeEach
void setup() {
DeviceProductEntity product = new DeviceProductEntity();
product.setMetadata("{}");
product.setTransportProtocol("MQTT");
product.setMessageProtocol("test");
product.setId(productId = "deviceinstancecontrollertest_product");
product.setName("DeviceInstanceControllerTest");
JetLinksDeviceMetadata metadata = new JetLinksDeviceMetadata("Test", "Test");
{
SimplePropertyMetadata metric = SimplePropertyMetadata.of(
"metric", "Metric", FloatType.GLOBAL
);
metric.setExpands(
PropertyMetadataConstants.Metrics
.metricsToExpands(Arrays.asList(
PropertyMetric.of("max", "最大值", 100),
PropertyMetric.of("min", "最小值", -100)
))
);
metadata.addProperty(metric);
}
product.setMetadata(this.metadata=JetLinksDeviceMetadataCodec.getInstance().doEncode(metadata));
productService
.save(product)
.then(productService.deploy(productId))
.then()
.as(StepVerifier::create)
.expectComplete()
.verify();
DeviceInstanceEntity device = new DeviceInstanceEntity();
device.setId(deviceId = "deviceinstancecontrollertest_device");
device.setName("DeviceInstanceControllerTest");
device.setProductId(product.getId());
device.setProductName(device.getName());
client
.patch()
.uri("/device/instance")
.bodyValue(device)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.put()
.uri("/device/instance/batch/_deploy")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Arrays.asList(deviceId))
.exchange()
.expectStatus()
.is2xxSuccessful();
}
@AfterEach
void shutdown() {
client
.put()
.uri("/device/instance/batch/_unDeploy")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Arrays.asList(deviceId))
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.put()
.uri("/device/instance/batch/_delete")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Arrays.asList(deviceId))
.exchange()
.expectStatus()
.is2xxSuccessful();
}
@Test
@SneakyThrows
void testCommon() {
// {
// DeviceInstanceEntity device = new DeviceInstanceEntity();
// device.setId(deviceId);
// device.setName("DeviceInstanceControllerTest");
// device.setProductId(productId);
// device.setProductName(device.getName());
// //重复创建
// client
// .post()
// .uri("/device/instance")
// .bodyValue(device)
// .exchange()
// .expectStatus()
// .is4xxClientError();
// }
client
.get()
.uri("/device/instance/{id:.+}/detail", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.get()
.uri("/device/instance/bind-providers")
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.get()
.uri("/device/instance/{deviceId}/state", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.get()
.uri("/device/instance/state/_sync")
.accept(MediaType.TEXT_EVENT_STREAM)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.post()
.uri("/device/instance/{deviceId}/deploy", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.post()
.uri("/device/instance/{deviceId}/undeploy", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.post()
.uri("/device/instance/{deviceId}/disconnect", deviceId)
.exchange();
}
@Test
void testProperties() {
client
.get()
.uri("/device/instance/{deviceId:.+}/properties/_query?where=property is test", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.get()
.uri("/device/instance/{deviceId:.+}/properties/_query", deviceId)
.exchange()
.expectStatus()
.is4xxClientError();
client
.get()
.uri("/device/instance/{deviceId:.+}/properties/latest", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.get()
.uri("/device/instance/{deviceId:.+}/properties", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.post()
.uri("/device/instance/{deviceId:.+}/properties/_top/{numberOfTop}", deviceId, 1)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{}")
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.get()
.uri("/device/instance/{deviceId:.+}/property/{property}/_query", deviceId, "test")
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.post()
.uri("/device/instance/{deviceId:.+}/property/{property}/_query", deviceId, "test")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{}")
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.post()
.uri("/device/instance/{deviceId:.+}/properties/_query", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{}")
.exchange();
client
.get()
.uri("/device/instance/{deviceId:.+}/property/{property:.+}", deviceId, "test")
.exchange()
.expectStatus()
.is2xxSuccessful();
AggRequest request = new AggRequest();
request.setColumns(Arrays.asList(
new DeviceDataService.DevicePropertyAggregation("test", "alias", Aggregation.AVG)
));
request.setQuery(DeviceDataService.AggregationRequest
.builder()
.build());
client
.post()
.uri("/device/instance/{deviceId:.+}/agg/_query", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.exchange();
}
@Test
void testEvent() {
client
.get()
.uri("/device/instance/{deviceId:.+}/event/{eventId}", deviceId, "test")
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.post()
.uri("/device/instance/{deviceId:.+}/event/{eventId}", deviceId, "test")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{}")
.exchange()
.expectStatus()
.is2xxSuccessful();
}
@Test
void testLog() {
client
.get()
.uri("/device/instance/{deviceId:.+}/logs", deviceId, "test")
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.post()
.uri("/device/instance/{deviceId:.+}/logs", deviceId, "test")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{}")
.exchange()
.expectStatus()
.is2xxSuccessful();
}
@Test
void testTag() {
DeviceTagEntity tag = new DeviceTagEntity();
tag.setKey("test");
tag.setValue("value");
client
.patch()
.uri("/device/instance/{deviceId}/tag", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(tag)
.exchange()
.expectStatus()
.is2xxSuccessful();
List<DeviceTagEntity> tags = client
.get()
.uri("/device/instance/{deviceId}/tags", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBodyList(DeviceTagEntity.class)
.returnResult()
.getResponseBody();
assertNotNull(tags);
assertFalse(tags.isEmpty());
client
.delete()
.uri("/device/instance/{deviceId}/tag/{tagId}", deviceId, tags.get(0).getId())
.exchange()
.expectStatus()
.is2xxSuccessful();
}
@Test
@SneakyThrows
void testMetadata() {
client
.get()
.uri("/device/instance/{id:.+}/config-metadata", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.get()
.uri("/device/instance/{id:.+}/config-metadata/{metadataType}/{metadataId}/{typeId}",
deviceId,
"property",
"temp",
"test")
.exchange()
.expectStatus()
.is2xxSuccessful();
String metadata = client
.post()
.uri("/device/instance/{deviceId}/property-metadata/import?fileUrl=" + new ClassPathResource("property-metadata.csv")
.getFile()
.getAbsolutePath(), deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBody(String.class)
.returnResult()
.getResponseBody();
assertNotNull(metadata);
client
.put()
.uri("/device/instance/{id:.+}/metadata", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(metadata)
.exchange()
.expectStatus()
.is2xxSuccessful();
deviceService.findById(deviceId)
.as(StepVerifier::create)
.expectNextMatches(device -> Objects.equals(
device.getDeriveMetadata(),
metadata
))
.expectComplete()
.verify();
client
.put()
.uri("/device/instance/{id}/metadata/merge-product", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.delete()
.uri("/device/instance/{id}/metadata", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful();
deviceService
.findById(deviceId)
.as(StepVerifier::create)
.expectNextMatches(device -> StringUtils.isEmpty(device.getDeriveMetadata()))
.expectComplete()
.verify();
}
@Test
void testConfiguration() {
deviceService.deploy(deviceId)
.then()
.as(StepVerifier::create)
.expectComplete()
.verify();
client
.post()
.uri("/device/instance/{deviceId:.+}/configuration/_write", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{\"test\":\"123\"}")
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.post()
.uri("/device/instance/{deviceId:.+}/configuration/_read", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("[\"test\"]")
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBody(Map.class)
.isEqualTo(Collections.singletonMap("test", "123"));
client
.put()
.uri("/device/instance/{deviceId:.+}/configuration/_reset", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.put()
.uri("/device/instance/{deviceId:.+}/shadow", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{\"test\":\"123\"}")
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.get()
.uri("/device/instance/{deviceId:.+}/shadow", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBody(String.class)
.isEqualTo("{\"test\":\"123\"}");
}
@Test
void testCommand() {
client
.put()
.uri("/device/instance/{deviceId:.+}/property", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Collections.singletonMap("test", "value"))
.exchange();
client
.post()
.uri("/device/instance/{deviceId:.+}/property/_read", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Collections.singleton("test"))
.exchange();
client
.post()
.uri("/device/instance/{deviceId:.+}/function/test", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Collections.singletonMap("test", "value"))
.exchange();
client
.post()
.uri("/device/instance/{deviceId:.+}/message", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Collections.singletonMap("properties", Collections.singletonList("test")))
.exchange()
.expectStatus()
.is2xxSuccessful();
Map<String, Object> data = new HashMap<>();
data.put("deviceId", "test");
data.put("properties", Collections.singletonList("test"));
client
.post()
.uri("/device/instance/messages", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(data)
.exchange()
.expectStatus()
.is2xxSuccessful();
}
@Test
void testAutoChangeProductInfo() {
client.post()
.uri("/device/instance")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{\"id\":\"testAutoChangeProductInfo\",\"name\":\"Test\",\"productId\":\"" + productId + "\"}")
.exchange()
.expectStatus()
.is2xxSuccessful();
}
@Autowired
private RelationService relationService;
@Test
void testRelation() {
RelationEntity entity = new RelationEntity();
entity.setRelation("manager");
entity.setObjectType("device");
entity.setObjectTypeName("设备");
entity.setTargetType("user");
entity.setTargetTypeName("用户");
entity.setName("管理员");
relationService
.save(entity).block();
client.get()
.uri("/device/instance/{deviceId}/detail", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBody()
.jsonPath("$.relations[0].relation").isEqualTo("manager")
.jsonPath("$.relations[0].related").isEmpty();
client.patch()
.uri("/device/instance/{deviceId}/relations", deviceId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(SaveRelationRequest.of(
"user",
"manager",
Arrays.asList(RelatedObjectInfo.of("admin", "管理员")),
null
))
.exchange()
.expectStatus()
.is2xxSuccessful();
client.get()
.uri("/device/instance/{deviceId}/detail", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBody()
.jsonPath("$.relations[0].relation").isEqualTo("manager")
.jsonPath("$.relations[0].related[0].id").isEqualTo("admin")
.jsonPath("$.relations[0].related[0].name").isEqualTo("管理员");
}
@Test
void testMetric() {
client.get()
.uri("/device/instance/{deviceId}/metric/property/{property}", deviceId, "metric")
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBody()
.jsonPath("[0].id").isEqualTo("max")
.jsonPath("[0].value").isEqualTo(100)
.jsonPath("[1].id").isEqualTo("min")
.jsonPath("[1].value").isEqualTo(-100);
client.patch()
.uri("/device/instance/{deviceId}/metric/property/{property}", deviceId, "metric")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(PropertyMetric.of("max", "最大值", 110))
.exchange()
.expectStatus()
.is2xxSuccessful();
client.get()
.uri("/device/instance/{deviceId}/metric/property/{property}", deviceId, "metric")
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBody()
.jsonPath("[0].id").isEqualTo("max")
.jsonPath("[0].value").isEqualTo(110)
.jsonPath("[1].id").isEqualTo("min")
.jsonPath("[1].value").isEqualTo(-100);
}
@Test
void testDetail(){
client
.get()
.uri("/device/instance/{deviceId}/detail", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBody()
.jsonPath("$.metadata").isEqualTo(metadata);
String newMetadata="{\"properties\":[]}";
client
.put()
.uri("/device/product/{productId}", productId)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{\"metadata\":"+ JSON.toJSONString(newMetadata)+"}")
.exchange()
.expectStatus()
.is2xxSuccessful();
//仅保存 未发布
client
.get()
.uri("/device/instance/{deviceId}/detail", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBody()
.jsonPath("$.metadata").isEqualTo(metadata);
client
.post()
.uri("/device/product/{productId}/deploy", productId)
.exchange()
.expectStatus()
.is2xxSuccessful();
client
.get()
.uri("/device/instance/{deviceId}/detail", deviceId)
.exchange()
.expectStatus()
.is2xxSuccessful()
.expectBody()
.jsonPath("$.metadata").isEqualTo(newMetadata);
}
}

View File

@ -11,5 +11,7 @@ COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/application/ ./
COPY docker-entrypoint.sh ./
COPY entrypoint.sh ./
RUN chmod +x docker-entrypoint.sh
RUN chmod +x entrypoint.sh
ENTRYPOINT ["./docker-entrypoint.sh"]