/*
* Copyright 2014-2016 CyberVision, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kaaproject.kaa.server.operations.service.akka.actors.core;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kaaproject.kaa.common.dto.ApplicationDto;
import org.kaaproject.kaa.common.dto.EndpointProfileDataDto;
import org.kaaproject.kaa.common.dto.EndpointProfileSchemaDto;
import org.kaaproject.kaa.common.dto.ServerProfileSchemaDto;
import org.kaaproject.kaa.common.dto.ctl.CTLSchemaDto;
import org.kaaproject.kaa.common.dto.ctl.CtlSchemaMetaInfoDto;
import org.kaaproject.kaa.common.dto.logs.LogSchemaDto;
import org.kaaproject.kaa.server.common.dao.ApplicationService;
import org.kaaproject.kaa.server.common.dao.CtlService;
import org.kaaproject.kaa.server.common.log.shared.appender.LogAppender;
import org.kaaproject.kaa.server.common.log.shared.appender.LogEvent;
import org.kaaproject.kaa.server.common.log.shared.appender.LogEventPack;
import org.kaaproject.kaa.server.common.log.shared.appender.LogSchema;
import org.kaaproject.kaa.server.common.log.shared.appender.data.BaseLogEventPack;
import org.kaaproject.kaa.server.operations.service.akka.AkkaContext;
import org.kaaproject.kaa.server.operations.service.akka.actors.core.ApplicationLogActorMessageProcessor.VoidCallback;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.logs.AbstractActorCallback;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.logs.LogEventPackMessage;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.logs.MultiLogDeliveryCallback;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.logs.SingleLogDeliveryCallback;
import org.kaaproject.kaa.server.operations.service.cache.AppVersionKey;
import org.kaaproject.kaa.server.operations.service.cache.CacheService;
import org.kaaproject.kaa.server.operations.service.logs.LogAppenderService;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author Bohdan Khablenko
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({AbstractActorCallback.class, SingleLogDeliveryCallback.class, MultiLogDeliveryCallback.class})
public class ApplicationLogActorMessageProcessorTest {
public static final String DEFAULT_FQN = "org.kaaproject.kaa.ctl.TestSchema";
private static final String APPLICATION_ID = "application_id";
private static final String APPLICATION_TOKEN = "application_token";
private static final String ENDPOINT_ID = "endpoint_id";
private static final String ENDPOINT_KEY = "endpoint_key";
private static final int LOG_SCHEMA_VERSION = 1;
private static final int CLIENT_PROFILE_SCHEMA_ID = 100;
private static final String CLIENT_PROFILE_SCHEMA_CTL_SCHEMA_ID = "1000";
private static final int SERVER_PROFILE_SCHEMA_ID = 200;
private static final String SERVER_PROFILE_SCHEMA_CTL_SCHEMA_ID = "2000";
private static final int REQUEST_ID = 100;
private static final int REQUIRED_APPENDERS_COUNT = 2;
private AkkaContext context;
private ApplicationDto application;
private ApplicationService applicationService;
private LogSchema logSchema;
private LogAppender[] required;
private LogAppender optional;
private List<LogAppender> logAppenders;
private LogAppenderService logAppenderService;
private CacheService cacheService;
private CtlService ctlService;
private LogEventPackMessage message;
@Before
public void before() throws Exception {
// Application
application = Mockito.mock(ApplicationDto.class);
Mockito.when(application.getId()).thenReturn(APPLICATION_ID);
// Application service
applicationService = Mockito.mock(ApplicationService.class);
Mockito.when(applicationService.findAppByApplicationToken(APPLICATION_TOKEN)).thenReturn(application);
// Log schema
LogSchemaDto logSchemaDto = new LogSchemaDto();
logSchemaDto.setVersion(LOG_SCHEMA_VERSION);
logSchema = new LogSchema(logSchemaDto, "");
// Log appenders
required = new LogAppender[REQUIRED_APPENDERS_COUNT];
for (int i = 0; i < required.length; i++) {
required[i] = Mockito.mock(LogAppender.class);
Mockito.when(required[i].getAppenderId()).thenReturn(Integer.toString(i * 100));
Mockito.when(required[i].isDeliveryConfirmationRequired()).thenReturn(Boolean.TRUE);
Mockito.when(required[i].isSchemaVersionSupported(Mockito.anyInt())).thenReturn(Boolean.TRUE);
}
optional = Mockito.mock(LogAppender.class);
Mockito.when(optional.getAppenderId()).thenReturn(Integer.toString(required.length * 100));
Mockito.when(optional.isDeliveryConfirmationRequired()).thenReturn(Boolean.FALSE);
Mockito.when(optional.isSchemaVersionSupported(Mockito.anyInt())).thenReturn(Boolean.TRUE);
// Log appender service
logAppenders = new ArrayList<>();
logAppenderService = Mockito.mock(LogAppenderService.class);
Mockito.when(logAppenderService.getApplicationAppenders(APPLICATION_ID)).thenReturn(logAppenders);
Mockito.when(logAppenderService.getLogSchema(APPLICATION_ID, LOG_SCHEMA_VERSION)).thenReturn(logSchema);
// Endpoint profile schema
EndpointProfileSchemaDto endpointProfileSchema = new EndpointProfileSchemaDto();
endpointProfileSchema.setId(Integer.toString(CLIENT_PROFILE_SCHEMA_ID));
endpointProfileSchema.setCtlSchemaId(CLIENT_PROFILE_SCHEMA_CTL_SCHEMA_ID);
// Server profile schema
ServerProfileSchemaDto serverProfileSchema = new ServerProfileSchemaDto();
serverProfileSchema.setId(Integer.toString(SERVER_PROFILE_SCHEMA_ID));
serverProfileSchema.setCtlSchemaId(SERVER_PROFILE_SCHEMA_CTL_SCHEMA_ID);
cacheService = Mockito.mock(CacheService.class);
ctlService = Mockito.mock(CtlService.class);
// Cache services
AppVersionKey key;
key = new AppVersionKey(APPLICATION_TOKEN, CLIENT_PROFILE_SCHEMA_ID);
Mockito.when(cacheService.getProfileSchemaByAppAndVersion(key)).thenReturn(endpointProfileSchema);
key = new AppVersionKey(APPLICATION_TOKEN, SERVER_PROFILE_SCHEMA_ID);
Mockito.when(cacheService.getServerProfileSchemaByAppAndVersion(key)).thenReturn(serverProfileSchema);
// Endpoint profile CTL schema
CTLSchemaDto endpointProfileCTLSchema = new CTLSchemaDto();
endpointProfileCTLSchema.setId(CLIENT_PROFILE_SCHEMA_CTL_SCHEMA_ID);
Mockito.when(cacheService.getCtlSchemaById(CLIENT_PROFILE_SCHEMA_CTL_SCHEMA_ID)).thenReturn(endpointProfileCTLSchema);
// Server profile CTL schema
CTLSchemaDto serverProfileCTLSchema = new CTLSchemaDto();
serverProfileCTLSchema.setId(SERVER_PROFILE_SCHEMA_CTL_SCHEMA_ID);
Mockito.when(cacheService.getCtlSchemaById(SERVER_PROFILE_SCHEMA_CTL_SCHEMA_ID)).thenReturn(serverProfileCTLSchema);
Mockito.when(ctlService.flatExportAsString(endpointProfileCTLSchema)).thenReturn("Client Profile CTL Schema");
Mockito.when(ctlService.flatExportAsString(serverProfileCTLSchema)).thenReturn("Server Profile CTL Schema");
// Akka Context
context = Mockito.mock(AkkaContext.class);
Mockito.when(context.getApplicationService()).thenReturn(applicationService);
Mockito.when(context.getCacheService()).thenReturn(cacheService);
Mockito.when(context.getCtlService()).thenReturn(ctlService);
Mockito.when(context.getLogAppenderService()).thenReturn(logAppenderService);
// Log event pack message
EndpointProfileDataDto endpoint = new EndpointProfileDataDto(ENDPOINT_ID, ENDPOINT_KEY, CLIENT_PROFILE_SCHEMA_ID, "", SERVER_PROFILE_SCHEMA_ID, "");
BaseLogEventPack pack = new BaseLogEventPack(endpoint, System.currentTimeMillis(), LOG_SCHEMA_VERSION, new ArrayList<LogEvent>());
message = new LogEventPackMessage(REQUEST_ID, ActorRef.noSender(), pack);
}
protected CTLSchemaDto generateCTLSchemaDto(String tenantId) {
return generateCTLSchemaDto(DEFAULT_FQN, tenantId, null, 100);
}
protected CTLSchemaDto generateCTLSchemaDto(String fqn, String tenantId, String applicationId, int version) {
CTLSchemaDto ctlSchema = new CTLSchemaDto();
ctlSchema.setMetaInfo(new CtlSchemaMetaInfoDto(fqn, tenantId, applicationId));
ctlSchema.setVersion(version);
String name = fqn.substring(fqn.lastIndexOf(".") + 1);
String namespace = fqn.substring(0, fqn.lastIndexOf("."));
StringBuilder body = new StringBuilder("{\"type\": \"record\",");
body = body.append("\"name\": \"").append(name).append("\",");
body = body.append("\"namespace\": \"").append(namespace).append("\",");
body = body.append("\"version\": ").append(version).append(",");
body = body.append("\"dependencies\": [], \"fields\": []}");
ctlSchema.setBody(body.toString());
return ctlSchema;
}
/**
* A test to ensure that the endpoint receives a response when there is
* exactly one log appender that requires delivery confirmation. In such
* case, the actor uses {@link SingleLogDeliveryCallback}
*/
@Test
public void withSingleRequiredDeliveryConfirmationTest() throws Exception {
logAppenders.add(required[0]);
logAppenders.add(optional);
SingleLogDeliveryCallback callback = Mockito.spy(new SingleLogDeliveryCallback(message.getOriginator(), message.getRequestId()));
PowerMockito.whenNew(SingleLogDeliveryCallback.class).withArguments(Mockito.any(), Mockito.anyInt()).thenReturn(callback);
Mockito.doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
callback.onSuccess();
return null;
}
}).when(required[0]).doAppend(Mockito.any(LogEventPack.class), Mockito.any(SingleLogDeliveryCallback.class));
ApplicationLogActorMessageProcessor messageProcessor = new ApplicationLogActorMessageProcessor(context, APPLICATION_TOKEN);
messageProcessor.processLogEventPack(Mockito.mock(ActorContext.class), message);
PowerMockito.verifyPrivate(callback).invoke("sendSuccessToEndpoint");
}
/**
* A test to ensure that the endpoint receives a response when there are
* multiple log appenders that require delivery confirmation. In such case,
* the actor uses an instance of {@link MultiLogDeliveryCallback}
*/
@Test
public void withMultipleRequiredDeliveryConfirmationsTest() throws Exception {
Arrays.stream(required).forEach(logAppenders::add);
MultiLogDeliveryCallback object = new MultiLogDeliveryCallback(message.getOriginator(), message.getRequestId(), logAppenders.size());
MultiLogDeliveryCallback callback = Mockito.spy(object);
PowerMockito.whenNew(MultiLogDeliveryCallback.class).withArguments(Mockito.any(), Mockito.anyInt(), Mockito.anyInt()).thenReturn(callback);
Arrays.stream(required).forEach(appender -> {
Mockito.doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
callback.onSuccess();
return null;
}
}).when(appender).doAppend(Mockito.any(LogEventPack.class), Mockito.any(MultiLogDeliveryCallback.class));
});
ApplicationLogActorMessageProcessor messageProcessor = new ApplicationLogActorMessageProcessor(context, APPLICATION_TOKEN);
messageProcessor.processLogEventPack(Mockito.mock(ActorContext.class), message);
PowerMockito.verifyPrivate(callback).invoke("sendSuccessToEndpoint");
}
/**
* A test to ensure that the endpoint receives a response even when there
* are no log appenders that require delivery confirmation.
*/
@Test
public void withoutRequiredDeliveryConfirmationsTest() throws Exception {
logAppenders.add(optional);
ApplicationLogActorMessageProcessor messageProcessor = Mockito.spy(new ApplicationLogActorMessageProcessor(context, APPLICATION_TOKEN));
messageProcessor.processLogEventPack(Mockito.mock(ActorContext.class), message);
Mockito.verify(optional).doAppend(Mockito.eq(message.getLogEventPack()), Mockito.any(VoidCallback.class));
Mockito.verify(messageProcessor).sendSuccessMessageToEndpoint(message);
}
}