Commit 39453993c0a86f5ad8703e4fd6f0be0cf6ba594d

Authored by Alex Mukha
1 parent 511187ccbe
Exists in master

Added reporting endpoint to get data for repoId.

aggregation-service/src/main/java/org/alfresco/heartbeat/processor/IngestionTableEventProcessor.java
1 1 package org.alfresco.heartbeat.processor;
2 2  
  3 +import com.amazonaws.services.dynamodbv2.model.AttributeValue;
3 4 import com.amazonaws.services.lambda.runtime.Context;
4 5 import com.amazonaws.services.lambda.runtime.RequestHandler;
5 6 import com.amazonaws.services.lambda.runtime.events.DynamodbEvent;
6 7 import com.amazonaws.services.lambda.runtime.events.DynamodbEvent.DynamodbStreamRecord;
7 8 import org.alfresco.heartbeat.service.AggregationService;
8 9  
  10 +import java.util.Map;
  11 +
9 12 /**
10 13 * DynamoDB event lister Lambda. Aggregates the new data to form the latest view.
11 14 *
... ... @@ -20,14 +23,23 @@ public class IngestionTableEventProcessor implements RequestHandler<DynamodbEven
20 23 for (DynamodbStreamRecord record : ddbEvent.getRecords()){
21 24 try
22 25 {
23   - aggregationService.aggregateData(record.getDynamodb().getNewImage());
  26 + Map<String,AttributeValue> newImage = record.getDynamodb().getNewImage();
  27 + if (newImage != null)
  28 + {
  29 + aggregationService.aggregateData(newImage);
  30 + }
  31 + else
  32 + {
  33 + throw new IllegalArgumentException("The item does not have new image, it is not add/update operation.");
  34 + }
  35 +
24 36 }
25 37 catch (IllegalArgumentException iae)
26 38 {
27   - context.getLogger().log("[ERROR] The record [" + record.getEventName() + "]" + record.getEventID() + " is invalid" +
28   - " and will be skipped: " + record);
  39 + context.getLogger().log("[ERROR] The record [" + record.getEventName() + "]"
  40 + + record.getEventID() + " will be skipped: " + record);
29 41 }
30 42 }
31   - return "Successfully processed " + ddbEvent.getRecords().size() + " records.";
  43 + return "Processed " + ddbEvent.getRecords().size() + " records.";
32 44 }
33 45 }
... ...
heartbeat-template.yaml
... ... @@ -2,7 +2,28 @@
2 2 Transform: 'AWS::Serverless-2016-10-31'
3 3 Description: An AWS Serverless Heartbeat receiver
4 4 Resources:
5   - HeartbeatLambda:
  5 + ReportByRepoIdLambda:
  6 + Type: AWS::Serverless::Function
  7 + Properties:
  8 + Handler: org.alfresco.heartbeat.handler.GetByRepoIdRequestHandler
  9 + Runtime: java8
  10 + MemorySize: 512
  11 + Timeout: 15
  12 + CodeUri: ./reporting-service/target/heartbeat-reporting-2.0-SNAPSHOT.jar
  13 + Policies:
  14 + - AmazonDynamoDBReadOnlyAccess
  15 + Environment:
  16 + Variables:
  17 + TABLE_NAME:
  18 + Ref: AggregationTable
  19 + Events:
  20 + PostRequest:
  21 + Type: Api
  22 + Properties:
  23 + Path: /report/{repositoryId}
  24 + Method: get
  25 +
  26 + IngestLambda:
6 27 Type: AWS::Serverless::Function
7 28 Properties:
8 29 Handler: org.alfresco.heartbeat.handler.HeartbeatRequestHandler
... ... @@ -20,7 +41,7 @@ Resources:
20 41 PostRequest:
21 42 Type: Api
22 43 Properties:
23   - Path: /
  44 + Path: /ingest
24 45 Method: post
25 46  
26 47 IngestionTable:
... ...
... ... @@ -62,7 +62,8 @@
62 62  
63 63 <modules>
64 64 <module>aggregation-service</module>
65   - <module>ingestion-service</module>
  65 + <module>ingestion-service</module>
  66 + <module>reporting-service</module>
66 67 </modules>
67 68  
68   -</project>
69 69 \ No newline at end of file
  70 +</project>
... ...
reporting-service/pom.xml
... ... @@ -0,0 +1,61 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <artifactId>heartbeat-reporting</artifactId>
  7 + <version>2.0-SNAPSHOT</version>
  8 +
  9 + <parent>
  10 + <artifactId>heartbeat-parent</artifactId>
  11 + <groupId>org.alfresco</groupId>
  12 + <version>2.0-SNAPSHOT</version>
  13 + <relativePath>../pom.xml</relativePath>
  14 + </parent>
  15 +
  16 + <dependencies>
  17 + <dependency>
  18 + <groupId>com.amazonaws</groupId>
  19 + <artifactId>aws-lambda-java-core</artifactId>
  20 + </dependency>
  21 + <dependency>
  22 + <groupId>com.amazonaws</groupId>
  23 + <artifactId>aws-java-sdk-dynamodb</artifactId>
  24 + </dependency>
  25 + <dependency>
  26 + <groupId>com.fasterxml.jackson.core</groupId>
  27 + <artifactId>jackson-core</artifactId>
  28 + </dependency>
  29 + <dependency>
  30 + <groupId>junit</groupId>
  31 + <artifactId>junit</artifactId>
  32 + <scope>test</scope>
  33 + </dependency>
  34 + <dependency>
  35 + <groupId>org.mockito</groupId>
  36 + <artifactId>mockito-all</artifactId>
  37 + <scope>test</scope>
  38 + </dependency>
  39 + </dependencies>
  40 +
  41 + <build>
  42 + <plugins>
  43 + <plugin>
  44 + <groupId>org.apache.maven.plugins</groupId>
  45 + <artifactId>maven-shade-plugin</artifactId>
  46 + <configuration>
  47 + <createDependencyReducedPom>false</createDependencyReducedPom>
  48 + </configuration>
  49 + <executions>
  50 + <execution>
  51 + <phase>package</phase>
  52 + <goals>
  53 + <goal>shade</goal>
  54 + </goals>
  55 + </execution>
  56 + </executions>
  57 + </plugin>
  58 + </plugins>
  59 + </build>
  60 +
  61 +</project>
... ...
reporting-service/src/main/java/org/alfresco/heartbeat/handler/GetByRepoIdRequestHandler.java
... ... @@ -0,0 +1,37 @@
  1 +package org.alfresco.heartbeat.handler;
  2 +
  3 +import com.amazonaws.services.lambda.runtime.Context;
  4 +import com.amazonaws.services.lambda.runtime.RequestHandler;
  5 +import org.alfresco.heartbeat.http.HeartbeatRequest;
  6 +import org.alfresco.heartbeat.http.HeartbeatResponse;
  7 +import org.alfresco.heartbeat.service.ReportingService;
  8 +import org.apache.http.HttpStatus;
  9 +
  10 +/**
  11 + * Lambda serving GET requests by repository ID for reporting
  12 + *
  13 + * @author amukha
  14 + */
  15 +public class GetByRepoIdRequestHandler implements RequestHandler<HeartbeatRequest, HeartbeatResponse>
  16 +{
  17 +
  18 + @Override
  19 + public HeartbeatResponse handleRequest(HeartbeatRequest request, Context context)
  20 + {
  21 + context.getLogger().log("Received object: " + request);
  22 + ReportingService reportingService = ReportingService.getInstance();
  23 + HeartbeatResponse response = new HeartbeatResponse();
  24 + try
  25 + {
  26 + String responseBody = reportingService.getReport(request.getPathParameters().get("repositoryId"));
  27 + response.setStatusCode(HttpStatus.SC_OK);
  28 + response.setBody(responseBody);
  29 + }
  30 + catch (Exception e)
  31 + {
  32 + context.getLogger().log("Processing failed: " + e.getMessage());
  33 + response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
  34 + }
  35 + return response;
  36 + }
  37 +}
... ...
reporting-service/src/main/java/org/alfresco/heartbeat/http/HeartbeatRequest.java
... ... @@ -0,0 +1,61 @@
  1 +package org.alfresco.heartbeat.http;
  2 +
  3 +import java.io.Serializable;
  4 +import java.util.Map;
  5 +
  6 +/**
  7 + * Represents the request object
  8 + *
  9 + * @author amukha
  10 + */
  11 +public class HeartbeatRequest implements Serializable
  12 +{
  13 + private String httpMethod;
  14 + private String body;
  15 + private Map<String, String> pathParameters;
  16 +
  17 + public HeartbeatRequest()
  18 + {
  19 +
  20 + }
  21 +
  22 + public Map<String, String> getPathParameters()
  23 + {
  24 + return pathParameters;
  25 + }
  26 +
  27 + public void setPathParameters(Map<String, String> pathParameters)
  28 + {
  29 + this.pathParameters = pathParameters;
  30 + }
  31 +
  32 + public String getHttpMethod()
  33 + {
  34 + return httpMethod;
  35 + }
  36 +
  37 + public void setHttpMethod(String httpMethod)
  38 + {
  39 + this.httpMethod = httpMethod;
  40 + }
  41 +
  42 + public String getBody()
  43 + {
  44 + return body;
  45 + }
  46 +
  47 + public void setBody(String body)
  48 + {
  49 + this.body = body;
  50 + }
  51 +
  52 + @Override
  53 + public String toString()
  54 + {
  55 + return "HeartbeatRequest{" +
  56 + "httpMethod='" + httpMethod + '\'' +
  57 + ", body='" + body + '\'' +
  58 + ", pathParameters=" + pathParameters +
  59 + '}';
  60 + }
  61 +}
... ...
reporting-service/src/main/java/org/alfresco/heartbeat/http/HeartbeatResponse.java
... ... @@ -0,0 +1,51 @@
  1 +package org.alfresco.heartbeat.http;
  2 +
  3 +import java.io.Serializable;
  4 +import java.util.List;
  5 +
  6 +/**
  7 + * Represents the response object
  8 + *
  9 + * @author amukha
  10 + */
  11 +public class HeartbeatResponse implements Serializable
  12 +{
  13 + private int statusCode;
  14 + private List<String> headers;
  15 + private String body;
  16 +
  17 + public HeartbeatResponse()
  18 + {
  19 +
  20 + }
  21 +
  22 + public int getStatusCode()
  23 + {
  24 + return statusCode;
  25 + }
  26 +
  27 + public void setStatusCode(int statusCode)
  28 + {
  29 + this.statusCode = statusCode;
  30 + }
  31 +
  32 + public List<String> getHeaders()
  33 + {
  34 + return headers;
  35 + }
  36 +
  37 + public void setHeaders(List<String> headers)
  38 + {
  39 + this.headers = headers;
  40 + }
  41 +
  42 + public String getBody()
  43 + {
  44 + return body;
  45 + }
  46 +
  47 + public void setBody(String body)
  48 + {
  49 + this.body = body;
  50 + }
  51 +}
... ...
reporting-service/src/main/java/org/alfresco/heartbeat/service/ReportingDAO.java
... ... @@ -0,0 +1,72 @@
  1 +package org.alfresco.heartbeat.service;
  2 +
  3 +import com.amazonaws.SDKGlobalConfiguration;
  4 +import com.amazonaws.regions.Region;
  5 +import com.amazonaws.regions.Regions;
  6 +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
  7 +import com.amazonaws.services.dynamodbv2.document.DynamoDB;
  8 +import com.amazonaws.services.dynamodbv2.document.Item;
  9 +import com.amazonaws.services.dynamodbv2.document.ItemCollection;
  10 +import com.amazonaws.services.dynamodbv2.document.QueryOutcome;
  11 +import com.amazonaws.services.dynamodbv2.document.Table;
  12 +import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;
  13 +import com.amazonaws.services.dynamodbv2.document.utils.ValueMap;
  14 +
  15 +import java.util.ArrayList;
  16 +import java.util.Iterator;
  17 +import java.util.List;
  18 +import java.util.Map;
  19 +
  20 +/**
  21 + * DAO layer
  22 + *
  23 + * @author amukha
  24 + */
  25 +public class ReportingDAO
  26 +{
  27 + static final String TABLE_NAME_KEY = "TABLE_NAME";
  28 +
  29 + private Table table;
  30 +
  31 + /**
  32 + * The CF uses environment variable to set the table name to lambda during creation of the stack.
  33 + * This method will return the table name from the environment variable in case of amazon deployment.
  34 + * @return the table name from the environment variable of amazon deployment.
  35 + */
  36 + public String getTableName()
  37 + {
  38 + return System.getenv(TABLE_NAME_KEY);
  39 + }
  40 +
  41 + public ReportingDAO()
  42 + {
  43 + AmazonDynamoDBClient client = new AmazonDynamoDBClient();
  44 + String envRegion = System.getenv(SDKGlobalConfiguration.AWS_REGION_ENV_VAR);
  45 + client.setRegion(Region.getRegion(Regions.fromName(envRegion)));
  46 + DynamoDB dynamoDB = new DynamoDB(client);
  47 + table = dynamoDB.getTable(getTableName());
  48 + }
  49 +
  50 + public ReportingDAO(DynamoDB dynamoDB, String tableName)
  51 + {
  52 + table = dynamoDB.getTable(tableName);
  53 + }
  54 +
  55 + /**
  56 + * The method creates or replaces the existing item in DB
  57 + */
  58 + public List<Map<String,Object>> getItems(String repositoryId)
  59 + {
  60 + QuerySpec spec = new QuerySpec()
  61 + .withKeyConditionExpression(ReportingService.REPOSITORY_ID_KEY + " = :v_id")
  62 + .withValueMap(new ValueMap().withString(":v_id", repositoryId));
  63 + ItemCollection<QueryOutcome> items = table.query(spec);
  64 + Iterator<Item> iterator = items.iterator();
  65 + List<Map<String,Object>> result = new ArrayList<>(items.getAccumulatedItemCount());
  66 + while (iterator.hasNext())
  67 + {
  68 + result.add(iterator.next().asMap());
  69 + }
  70 + return result;
  71 + }
  72 +}
... ...
reporting-service/src/main/java/org/alfresco/heartbeat/service/ReportingService.java
... ... @@ -0,0 +1,96 @@
  1 +package org.alfresco.heartbeat.service;
  2 +
  3 +
  4 +import com.fasterxml.jackson.databind.ObjectMapper;
  5 +import com.fasterxml.jackson.databind.node.ArrayNode;
  6 +import com.fasterxml.jackson.databind.node.ObjectNode;
  7 +
  8 +import java.io.IOException;
  9 +import java.util.List;
  10 +import java.util.Map;
  11 +
  12 +/**
  13 + * Service implementation
  14 + *
  15 + * @author amukha
  16 + */
  17 +public class ReportingService
  18 +{
  19 + public static final String REPOSITORY_ID_KEY = "repositoryId";
  20 + public static final String FEATURE_KEY = "feature";
  21 + public static final String VERSION_KEY = "version";
  22 + public static final String PAYLOAD_KEY = "payload";
  23 + public static final String ITEMS_KEY = "items";
  24 +
  25 + private ReportingDAO reportingDAO;
  26 +
  27 + private static ReportingService instance;
  28 +
  29 + /**
  30 + * Default constructor for testing purposes.
  31 + * {@link ReportingService#getInstance()} should be used instead of this.
  32 + */
  33 + ReportingService()
  34 + {
  35 + }
  36 +
  37 + private ReportingService(ReportingDAO reportingDAO)
  38 + {
  39 + this.reportingDAO = reportingDAO;
  40 + }
  41 +
  42 + public static ReportingService getInstance()
  43 + {
  44 + if (instance == null)
  45 + {
  46 + instance = new ReportingService(new ReportingDAO());
  47 + }
  48 + return instance;
  49 + }
  50 +
  51 + /**
  52 + * Setter for testing purposes
  53 + */
  54 + public void setReportingDAO(ReportingDAO reportingDAO)
  55 + {
  56 + this.reportingDAO = reportingDAO;
  57 + }
  58 +
  59 + /**
  60 + * @return A valid JSON response containing all features for the provided repositoryId
  61 + */
  62 + public String getReport(String repositoryId) throws IllegalArgumentException, IOException
  63 + {
  64 + if (!isMandatoryDataValid(repositoryId))
  65 + {
  66 + throw new IllegalArgumentException("The repository ID is not valid: " + repositoryId);
  67 + }
  68 + List<Map<String,Object>> items = reportingDAO.getItems(repositoryId);
  69 + ObjectMapper mapper = new ObjectMapper();
  70 + ObjectNode responseNode = mapper.createObjectNode();
  71 + ArrayNode returnItems = responseNode.putArray(ITEMS_KEY);
  72 + for (Map<String,Object> item : items)
  73 + {
  74 + ObjectNode itemNode = mapper.createObjectNode();
  75 + returnItems.add(itemNode);
  76 + for (String key : item.keySet())
  77 + {
  78 + // all the values are currently strings
  79 + if (key.equals(PAYLOAD_KEY))
  80 + {
  81 + itemNode.set(PAYLOAD_KEY, mapper.readTree((String) item.get(key)));
  82 + }
  83 + else
  84 + {
  85 + itemNode.put(key, (String) item.get(key));
  86 + }
  87 + }
  88 + }
  89 + return responseNode.toString();
  90 + }
  91 +
  92 + private boolean isMandatoryDataValid(String repositoryId)
  93 + {
  94 + return !(repositoryId == null || repositoryId.isEmpty());
  95 + }
  96 +}
... ...
reporting-service/src/test/java/org/alfresco/heartbeat/service/ReportingDAOTest.java
... ... @@ -0,0 +1,160 @@
  1 +package org.alfresco.heartbeat.service;
  2 +
  3 +import com.amazonaws.regions.Region;
  4 +import com.amazonaws.regions.Regions;
  5 +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
  6 +import com.amazonaws.services.dynamodbv2.document.DynamoDB;
  7 +import com.amazonaws.services.dynamodbv2.document.Item;
  8 +import com.amazonaws.services.dynamodbv2.document.Table;
  9 +import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
  10 +import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
  11 +import com.amazonaws.services.dynamodbv2.model.KeyType;
  12 +import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
  13 +import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
  14 +import com.fasterxml.jackson.databind.ObjectMapper;
  15 +import com.fasterxml.jackson.databind.node.ObjectNode;
  16 +import org.junit.AfterClass;
  17 +import org.junit.BeforeClass;
  18 +import org.junit.Test;
  19 +
  20 +import java.util.ArrayList;
  21 +import java.util.List;
  22 +import java.util.Map;
  23 +
  24 +import static org.junit.Assert.assertEquals;
  25 +import static org.junit.Assert.assertNotNull;
  26 +import static org.junit.Assert.assertTrue;
  27 +
  28 +/**
  29 + * Integration tests for {@link ReportingDAO}
  30 + *
  31 + * @author amukha
  32 + */
  33 +public class ReportingDAOTest
  34 +{
  35 + private static DynamoDB dynamoDB;
  36 + private static final String TABLE_NAME = ReportingDAOTest.class.getName() + "-" + System.currentTimeMillis();
  37 +
  38 + @BeforeClass
  39 + public static void beforeClass() throws Exception
  40 + {
  41 + AmazonDynamoDBClient client = new AmazonDynamoDBClient();
  42 + // hardcoded region for tests
  43 + client.setRegion(Region.getRegion(Regions.EU_WEST_1));
  44 + dynamoDB = new DynamoDB(client);
  45 + createTable(TABLE_NAME);
  46 + }
  47 +
  48 + @AfterClass
  49 + public static void afterClass() throws Exception
  50 + {
  51 + Table table = dynamoDB.getTable(TABLE_NAME);
  52 + table.delete();
  53 + table.waitForDelete();
  54 + }
  55 +
  56 +
  57 + private static void createTable(String tableName) throws Exception
  58 + {
  59 + List<KeySchemaElement> keySchema = new ArrayList<>();
  60 + keySchema.add(new KeySchemaElement(ReportingService.REPOSITORY_ID_KEY, KeyType.HASH));
  61 + keySchema.add(new KeySchemaElement(ReportingService.FEATURE_KEY, KeyType.RANGE));
  62 + List<AttributeDefinition> attributeDefinitions = new ArrayList<>();
  63 + attributeDefinitions.add(new AttributeDefinition(ReportingService.REPOSITORY_ID_KEY, ScalarAttributeType.S));
  64 + attributeDefinitions.add(new AttributeDefinition(ReportingService.FEATURE_KEY, ScalarAttributeType.S));
  65 + Table table = dynamoDB.createTable(
  66 + tableName,
  67 + keySchema,
  68 + attributeDefinitions,
  69 + new ProvisionedThroughput(new Long(5), new Long(5)));
  70 + table.waitForActive();
  71 + }
  72 +
  73 + @Test
  74 + public void verifyTableExists()
  75 + {
  76 + Table table = dynamoDB.getTable(TABLE_NAME);
  77 + assertNotNull("The table should not be null.", table);
  78 + }
  79 +
  80 + @Test
  81 + public void queryItemFromOne()
  82 + {
  83 + String repositoryId = String.format(ReportingServiceTest.REPOSITORY_ID, 1);
  84 + String feature = String.format(ReportingServiceTest.FEATURE, 1);
  85 + String version = String.format(ReportingServiceTest.VERSION, 1);
  86 + ObjectMapper mapper = new ObjectMapper();
  87 + ObjectNode payloadNode = mapper.createObjectNode();
  88 + payloadNode.put("users", 100);
  89 + payloadNode.put("documents", 1000);
  90 + String payload = payloadNode.toString();
  91 +
  92 + ReportingDAO dao = new ReportingDAO(dynamoDB, TABLE_NAME);
  93 + Table table = dynamoDB.getTable(TABLE_NAME);
  94 + Item item = new Item()
  95 + .withPrimaryKey(ReportingService.REPOSITORY_ID_KEY, repositoryId)
  96 + .withString(ReportingService.FEATURE_KEY, feature)
  97 + .withString(ReportingService.VERSION_KEY, version)
  98 + .withString(ReportingService.PAYLOAD_KEY, payload);
  99 + table.putItem(item);
  100 + List<Map<String, Object>> items = dao.getItems(repositoryId);
  101 + assertNotNull("The items should not be null.", items);
  102 + assertTrue("The result should have one item.", items.size() == 1);
  103 + assertEquals("The values of the saved item should match.",
  104 + repositoryId, items.get(0).get(ReportingService.REPOSITORY_ID_KEY));
  105 + assertEquals("The values of the saved item should match.",
  106 + feature, items.get(0).get(ReportingService.FEATURE_KEY));
  107 + assertEquals("The values of the saved item should match.",
  108 + version, items.get(0).get(ReportingService.VERSION_KEY));
  109 + assertEquals("The values of the saved item should match.",
  110 + payload, items.get(0).get(ReportingService.PAYLOAD_KEY));
  111 + }
  112 +
  113 + @Test
  114 + public void queryItemFromMany()
  115 + {
  116 + String repositoryId = String.format(ReportingServiceTest.REPOSITORY_ID, 2);
  117 + String feature = String.format(ReportingServiceTest.FEATURE, 2);
  118 + String version = String.format(ReportingServiceTest.VERSION, 2);
  119 + ObjectMapper mapper = new ObjectMapper();
  120 + ObjectNode payloadNode = mapper.createObjectNode();
  121 + payloadNode.put("users", 100);
  122 + payloadNode.put("documents", 1000);
  123 + String payload = payloadNode.toString();
  124 + ReportingDAO dao = new ReportingDAO(dynamoDB, TABLE_NAME);
  125 + Table table = dynamoDB.getTable(TABLE_NAME);
  126 + Item item = new Item()
  127 + .withPrimaryKey(ReportingService.REPOSITORY_ID_KEY, repositoryId)
  128 + .withString(ReportingService.FEATURE_KEY, feature)
  129 + .withString(ReportingService.VERSION_KEY, version)
  130 + .withString(ReportingService.PAYLOAD_KEY, payload);
  131 + table.putItem(item);
  132 + // add another feature
  133 + String newFeature = String.format(ReportingServiceTest.FEATURE, 3);
  134 + item = new Item()
  135 + .withPrimaryKey(ReportingService.REPOSITORY_ID_KEY, repositoryId)
  136 + .withString(ReportingService.FEATURE_KEY, newFeature)
  137 + .withString(ReportingService.VERSION_KEY, version)
  138 + .withString(ReportingService.PAYLOAD_KEY, payload);
  139 + table.putItem(item);
  140 + List<Map<String, Object>> items = dao.getItems(repositoryId);
  141 + assertNotNull("The items should not be null.", items);
  142 + assertEquals("The result should have 2 items.", 2, items.size());
  143 + assertEquals("The values of the saved item should match.",
  144 + repositoryId, items.get(0).get(ReportingService.REPOSITORY_ID_KEY));
  145 + assertEquals("The values of the saved item should match.",
  146 + feature, items.get(0).get(ReportingService.FEATURE_KEY));
  147 + assertEquals("The values of the saved item should match.",
  148 + version, items.get(0).get(ReportingService.VERSION_KEY));
  149 + assertEquals("The values of the saved item should match.",
  150 + payload, items.get(0).get(ReportingService.PAYLOAD_KEY));
  151 + assertEquals("The values of the saved item should match.",
  152 + repositoryId, items.get(1).get(ReportingService.REPOSITORY_ID_KEY));
  153 + assertEquals("The values of the saved item should match.",
  154 + newFeature, items.get(1).get(ReportingService.FEATURE_KEY));
  155 + assertEquals("The values of the saved item should match.",
  156 + version, items.get(1).get(ReportingService.VERSION_KEY));
  157 + assertEquals("The values of the saved item should match.",
  158 + payload, items.get(1).get(ReportingService.PAYLOAD_KEY));
  159 + }
  160 +}
... ...
reporting-service/src/test/java/org/alfresco/heartbeat/service/ReportingServiceTest.java
... ... @@ -0,0 +1,157 @@
  1 +package org.alfresco.heartbeat.service;
  2 +
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import com.fasterxml.jackson.databind.ObjectMapper;
  5 +import com.fasterxml.jackson.databind.node.ArrayNode;
  6 +import com.fasterxml.jackson.databind.node.ObjectNode;
  7 +import org.junit.Before;
  8 +import org.junit.Test;
  9 +
  10 +import java.util.ArrayList;
  11 +import java.util.HashMap;
  12 +import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +import static org.junit.Assert.assertEquals;
  16 +import static org.junit.Assert.fail;
  17 +import static org.mockito.Mockito.mock;
  18 +import static org.mockito.Mockito.when;
  19 +
  20 +/**
  21 + * Tests for {@link ReportingService}
  22 + *
  23 + * @author amukha
  24 + */
  25 +public class ReportingServiceTest
  26 +{
  27 + private ReportingDAO dao = mock(ReportingDAO.class);
  28 + private ReportingService reportingService;
  29 +
  30 + static final String REPOSITORY_ID = "test-repository-id-%d";
  31 + static final String FEATURE = "Alfresco Content Services %d";
  32 + static final String VERSION = "5.2.%d";
  33 +
  34 + @Before
  35 + public void before()
  36 + {
  37 + reportingService = new ReportingService();
  38 + reportingService.setReportingDAO(dao);
  39 + }
  40 +
  41 + @Test
  42 + public void getReportWithNullRepoId() throws Exception
  43 + {
  44 + try
  45 + {
  46 + reportingService.getReport(null);
  47 + fail("The IllegalArgumentException should have been thrown.");
  48 + }
  49 + catch (IllegalArgumentException iae)
  50 + {
  51 + // expected
  52 + }
  53 + }
  54 +
  55 + @Test
  56 + public void getReportWithEmptyRepoId() throws Exception
  57 + {
  58 + try
  59 + {
  60 + reportingService.getReport("");
  61 + fail("The IllegalArgumentException should have been thrown.");
  62 + }
  63 + catch (IllegalArgumentException iae)
  64 + {
  65 + // expected
  66 + }
  67 + }
  68 +
  69 + @Test
  70 + public void returnValidJson() throws Exception
  71 + {
  72 + String repositoryId = String.format(REPOSITORY_ID, 1);
  73 + String feature = String.format(ReportingServiceTest.FEATURE, 1);
  74 + String version = String.format(ReportingServiceTest.VERSION, 1);
  75 + ObjectMapper mapper = new ObjectMapper();
  76 + ObjectNode payloadNode = mapper.createObjectNode();
  77 + payloadNode.put("users", 100);
  78 + payloadNode.put("documents", 1000);
  79 + String payload = payloadNode.toString();
  80 + List<Map<String, Object>> returnObject = new ArrayList<>(1);
  81 + Map<String, Object> returnObjectMap = new HashMap<>();
  82 + returnObject.add(returnObjectMap);
  83 + returnObjectMap.put(ReportingService.REPOSITORY_ID_KEY, repositoryId);
  84 + returnObjectMap.put(ReportingService.FEATURE_KEY, feature);
  85 + returnObjectMap.put(ReportingService.VERSION_KEY, version);
  86 + returnObjectMap.put(ReportingService.PAYLOAD_KEY, payload);
  87 +
  88 + when(dao.getItems(repositoryId)).thenReturn(returnObject);
  89 + String result = reportingService.getReport(repositoryId);
  90 + // Checks that the response is a valid JSON syntactically
  91 + JsonNode jsonNode = mapper.readTree(result);
  92 + ArrayNode arrayNode = (ArrayNode) jsonNode.get(ReportingService.ITEMS_KEY);
  93 + assertEquals("The response should have one item.", 1, arrayNode.size());
  94 + assertEquals("The values of the saved item should match.",
  95 + repositoryId, arrayNode.get(0).get(ReportingService.REPOSITORY_ID_KEY).asText());
  96 + assertEquals("The values of the saved item should match.",
  97 + feature, arrayNode.get(0).get(ReportingService.FEATURE_KEY).asText());
  98 + assertEquals("The values of the saved item should match.",
  99 + version, arrayNode.get(0).get(ReportingService.VERSION_KEY).asText());
  100 + assertEquals("The values of the saved item should match.",
  101 + payload, arrayNode.get(0).get(ReportingService.PAYLOAD_KEY).toString());
  102 + }
  103 +
  104 + @Test
  105 + public void returnValidJsonMultipleItems() throws Exception
  106 + {
  107 + String repositoryId = String.format(REPOSITORY_ID, 2);
  108 + String feature = String.format(ReportingServiceTest.FEATURE, 2);
  109 + String version = String.format(ReportingServiceTest.VERSION, 2);
  110 + ObjectMapper mapper = new ObjectMapper();
  111 + ObjectNode payloadNode = mapper.createObjectNode();
  112 + payloadNode.put("users", 100);
  113 + payloadNode.put("documents", 1000);
  114 + String payload = payloadNode.toString();
  115 + List<Map<String, Object>> returnObject = new ArrayList<>(2);
  116 + Map<String, Object> returnObjectMap = new HashMap<>();
  117 + returnObject.add(returnObjectMap);
  118 + returnObjectMap.put(ReportingService.REPOSITORY_ID_KEY, repositoryId);
  119 + returnObjectMap.put(ReportingService.FEATURE_KEY, feature);
  120 + returnObjectMap.put(ReportingService.VERSION_KEY, version);
  121 + returnObjectMap.put(ReportingService.PAYLOAD_KEY, payload);
  122 + // Add another feature
  123 + String newFeature = String.format(ReportingServiceTest.FEATURE, 3);
  124 + payloadNode = mapper.createObjectNode();
  125 + payloadNode.put("foo", "bar");
  126 + String newPayload = payloadNode.toString();
  127 + returnObjectMap = new HashMap<>();
  128 + returnObject.add(returnObjectMap);
  129 + returnObjectMap.put(ReportingService.REPOSITORY_ID_KEY, repositoryId);
  130 + returnObjectMap.put(ReportingService.FEATURE_KEY, newFeature);
  131 + returnObjectMap.put(ReportingService.VERSION_KEY, version);
  132 + returnObjectMap.put(ReportingService.PAYLOAD_KEY, newPayload);
  133 +
  134 + when(dao.getItems(repositoryId)).thenReturn(returnObject);
  135 + String result = reportingService.getReport(repositoryId);
  136 + // Checks that the response is a valid JSON syntactically
  137 + JsonNode jsonNode = mapper.readTree(result);
  138 + ArrayNode arrayNode = (ArrayNode) jsonNode.get(ReportingService.ITEMS_KEY);
  139 + assertEquals("The response should have two items.", 2, arrayNode.size());
  140 + assertEquals("The values of the saved item should match.",
  141 + repositoryId, arrayNode.get(0).get(ReportingService.REPOSITORY_ID_KEY).asText());
  142 + assertEquals("The values of the saved item should match.",
  143 + feature, arrayNode.get(0).get(ReportingService.FEATURE_KEY).asText());
  144 + assertEquals("The values of the saved item should match.",
  145 + version, arrayNode.get(0).get(ReportingService.VERSION_KEY).asText());
  146 + assertEquals("The values of the saved item should match.",
  147 + payload, arrayNode.get(0).get(ReportingService.PAYLOAD_KEY).toString());
  148 + assertEquals("The values of the saved item should match.",
  149 + repositoryId, arrayNode.get(1).get(ReportingService.REPOSITORY_ID_KEY).asText());
  150 + assertEquals("The values of the saved item should match.",
  151 + newFeature, arrayNode.get(1).get(ReportingService.FEATURE_KEY).asText());
  152 + assertEquals("The values of the saved item should match.",
  153 + version, arrayNode.get(1).get(ReportingService.VERSION_KEY).asText());
  154 + assertEquals("The values of the saved item should match.",
  155 + newPayload, arrayNode.get(1).get(ReportingService.PAYLOAD_KEY).toString());
  156 + }
  157 +}
... ...