diff --git a/provider/legal-azure/lombok.config b/provider/legal-azure/lombok.config
new file mode 100644
index 0000000000000000000000000000000000000000..df71bb6a0fb87825475c75620c4d5e30088ade7f
--- /dev/null
+++ b/provider/legal-azure/lombok.config
@@ -0,0 +1,2 @@
+config.stopBubbling = true
+lombok.addLombokGeneratedAnnotation = true
diff --git a/provider/legal-azure/pom.xml b/provider/legal-azure/pom.xml
index 64de20bf3a2a4c4e12698a246da713721a5463be..8487918e7fa5b02f908eab75f22bbce2d2544d13 100644
--- a/provider/legal-azure/pom.xml
+++ b/provider/legal-azure/pom.xml
@@ -116,6 +116,13 @@
             <scope>compile</scope>
         </dependency>
 
+        <!-- test -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
         <!--
          Override the spring-boot version of these dependencies to the ones
          required by the azure-core library. This needs to be done for each
diff --git a/provider/legal-azure/src/main/java/org/opengroup/osdu/legal/azure/jobs/LegalTagPublisherImpl.java b/provider/legal-azure/src/main/java/org/opengroup/osdu/legal/azure/jobs/LegalTagPublisherImpl.java
index 302e1ecc3a4c45003891e5c465f089432b16851f..72bb8c75770e93b6714306c4d559d35ff03554be 100644
--- a/provider/legal-azure/src/main/java/org/opengroup/osdu/legal/azure/jobs/LegalTagPublisherImpl.java
+++ b/provider/legal-azure/src/main/java/org/opengroup/osdu/legal/azure/jobs/LegalTagPublisherImpl.java
@@ -30,9 +30,9 @@ import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
 
-
 @Component
 public class LegalTagPublisherImpl implements ILegalTagPublisher {
+
     @Inject
     private ITopicClientFactory topicClientFactory;
 
diff --git a/provider/legal-azure/src/main/java/org/opengroup/osdu/legal/azure/security/WhoamiController.java b/provider/legal-azure/src/main/java/org/opengroup/osdu/legal/azure/security/WhoamiController.java
index 5a538259e9942e1d74577b4023a7a637c3fb812c..d3eb5ba7c72021b9a25c30a39e5bc7fe8fcc0013 100644
--- a/provider/legal-azure/src/main/java/org/opengroup/osdu/legal/azure/security/WhoamiController.java
+++ b/provider/legal-azure/src/main/java/org/opengroup/osdu/legal/azure/security/WhoamiController.java
@@ -14,18 +14,32 @@
 
 package org.opengroup.osdu.legal.azure.security;
 
+import lombok.NoArgsConstructor;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 
+@NoArgsConstructor
 @Controller
 public class WhoamiController {
+
+    private SecurityContext securityContext;
+
+    // Constructor made for unit testing
+    WhoamiController(SecurityContext securityContext) {
+        this.securityContext = securityContext;
+    }
+
     @RequestMapping(value = "/whoami")
     @ResponseBody
     public String whoami() {
-        final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+        // Added for unit testing
+        if (securityContext == null)
+            securityContext = SecurityContextHolder.getContext();
+        final Authentication auth = securityContext.getAuthentication();
 
         String userName = auth.getName();
         String roles = String.valueOf(auth.getAuthorities());
diff --git a/provider/legal-azure/src/test/java/org/opengroup/osdu/legal/azure/jobs/LegalTagPublisherImplTest.java b/provider/legal-azure/src/test/java/org/opengroup/osdu/legal/azure/jobs/LegalTagPublisherImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f71d4104d2c2b1818f805dc84ac0ed87f19f17b
--- /dev/null
+++ b/provider/legal-azure/src/test/java/org/opengroup/osdu/legal/azure/jobs/LegalTagPublisherImplTest.java
@@ -0,0 +1,103 @@
+//  Copyright © Microsoft Corporation
+//
+//  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.opengroup.osdu.legal.azure.jobs;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.microsoft.azure.servicebus.Message;
+import com.microsoft.azure.servicebus.MessageBody;
+import com.microsoft.azure.servicebus.TopicClient;
+import com.microsoft.azure.servicebus.primitives.ServiceBusException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.opengroup.osdu.azure.servicebus.ITopicClientFactory;
+import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
+import org.opengroup.osdu.core.common.model.http.DpsHeaders;
+import org.opengroup.osdu.core.common.model.legal.StatusChangedTags;
+
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LegalTagPublisherImplTest {
+
+    private static final String DATA_PARTITION_WITH_FALLBACK_ACCOUNT_ID = "data-partition-account-id";
+    private static final String CORRELATION_ID = "correlation-id";
+    private static final String USER_EMAIL = "user@email.com";
+    private static final String PARTITION_ID = "partition-id";
+
+    @Mock
+    private JaxRsDpsLog logger;
+
+    @Mock
+    private ITopicClientFactory topicClientFactory;
+
+    @Mock
+    private TopicClient topicClient;
+
+    @Mock
+    private DpsHeaders headers;
+
+    @InjectMocks
+    private LegalTagPublisherImpl sut;
+
+    @Before
+    public void init() throws ServiceBusException, InterruptedException {
+        doReturn(DATA_PARTITION_WITH_FALLBACK_ACCOUNT_ID).when(headers).getPartitionIdWithFallbackToAccountId();
+        doReturn(CORRELATION_ID).when(headers).getCorrelationId();
+        doReturn(USER_EMAIL).when(headers).getUserEmail();
+        doReturn(PARTITION_ID).when(headers).getPartitionId();
+        doReturn(topicClient).when(topicClientFactory).getClient(eq(PARTITION_ID), any());
+    }
+
+    @Test
+    public void testPublishLegalTag() throws Exception {
+        StatusChangedTags tags = new StatusChangedTags();
+        sut.publish("project-id", headers, tags);
+        ArgumentCaptor<Message> msg = ArgumentCaptor.forClass(Message.class);
+        ArgumentCaptor<String> log = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<Exception> exception = ArgumentCaptor.forClass(Exception.class);
+
+        verify(logger).info(log.capture());
+        assertEquals("Storage publishes message " + CORRELATION_ID, log.getValue());
+
+        verify(topicClient).send(msg.capture());
+        Map<String, Object> properties = msg.getValue().getProperties();
+
+        assertEquals(DATA_PARTITION_WITH_FALLBACK_ACCOUNT_ID, properties.get(DpsHeaders.ACCOUNT_ID));
+        assertEquals(DATA_PARTITION_WITH_FALLBACK_ACCOUNT_ID, properties.get(DpsHeaders.DATA_PARTITION_ID));
+        assertEquals(CORRELATION_ID, properties.get(DpsHeaders.CORRELATION_ID));
+        assertEquals(USER_EMAIL, properties.get(DpsHeaders.USER_EMAIL));
+
+        MessageBody messageBody = msg.getValue().getMessageBody();
+        Gson gson = new Gson();
+        String messageKey = "message";
+        String dataKey = "data";
+        JsonObject jsonObjectMessage = gson.fromJson(new String(messageBody.getBinaryData().get(0)), JsonObject.class);
+        JsonObject jsonObject = (JsonObject) jsonObjectMessage.get(messageKey);
+        assertEquals(DATA_PARTITION_WITH_FALLBACK_ACCOUNT_ID, jsonObject.get(DpsHeaders.ACCOUNT_ID).getAsString());
+        assertEquals(DATA_PARTITION_WITH_FALLBACK_ACCOUNT_ID, jsonObject.get(DpsHeaders.DATA_PARTITION_ID).getAsString());
+        assertEquals(CORRELATION_ID, jsonObject.get(DpsHeaders.CORRELATION_ID).getAsString());
+        assertEquals(USER_EMAIL, jsonObject.get(DpsHeaders.USER_EMAIL).getAsString());
+        assertEquals(gson.toJsonTree(tags), jsonObject.get(dataKey));
+    }
+}
diff --git a/provider/legal-azure/src/test/java/org/opengroup/osdu/legal/azure/security/WhoamiControllerTest.java b/provider/legal-azure/src/test/java/org/opengroup/osdu/legal/azure/security/WhoamiControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..756e375e2b3f34563afae4930817405f258e5072
--- /dev/null
+++ b/provider/legal-azure/src/test/java/org/opengroup/osdu/legal/azure/security/WhoamiControllerTest.java
@@ -0,0 +1,70 @@
+//  Copyright © Microsoft Corporation
+//
+//  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.opengroup.osdu.legal.azure.security;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+
+import java.util.*;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class WhoamiControllerTest {
+
+    private static final String userName = "username";
+    private static final String roles = "roles";
+    private static final String details = "details";
+
+    @InjectMocks
+    private WhoamiController sut;
+
+    private class DummyPrincipal {
+        @Override
+        public String toString() {
+            return details;
+        }
+    }
+
+    public class DummyAuthority extends ArrayList {
+        @Override
+        public String toString() {
+            return roles;
+        }
+    }
+
+    @Before
+    public void init() {
+        Authentication auth = mock(Authentication.class);
+        SecurityContext securityContext = mock(SecurityContext.class);
+        doReturn(auth).when(securityContext).getAuthentication();
+        doReturn(userName).when(auth).getName();
+        doReturn(new DummyAuthority()).when(auth).getAuthorities();
+        doReturn(new DummyPrincipal()).when(auth).getPrincipal();
+        sut = new WhoamiController(securityContext);
+    }
+
+    @Test
+    public void testWhoamiResponse() {
+        String response = sut.whoami();
+        assertEquals("user: " + userName + "<BR>roles: " + roles + "<BR>details: " + details + "<BR>", response);
+    }
+}
diff --git a/provider/legal-azure/src/test/java/org/opengroup/osdu/legal/azure/tags/dataaccess/LegalTagRepositoryImplTest.java b/provider/legal-azure/src/test/java/org/opengroup/osdu/legal/azure/tags/dataaccess/LegalTagRepositoryImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..70cfae98617e6ab1127bb8abdd190902a6bd864f
--- /dev/null
+++ b/provider/legal-azure/src/test/java/org/opengroup/osdu/legal/azure/tags/dataaccess/LegalTagRepositoryImplTest.java
@@ -0,0 +1,220 @@
+//  Copyright © Microsoft Corporation
+//
+//  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.opengroup.osdu.legal.azure.tags.dataaccess;
+
+import com.azure.cosmos.FeedOptions;
+import com.azure.cosmos.SqlParameter;
+import com.azure.cosmos.SqlParameterList;
+import com.azure.cosmos.SqlQuerySpec;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.opengroup.osdu.azure.cosmosdb.CosmosStore;
+import org.opengroup.osdu.core.common.model.http.AppException;
+import org.opengroup.osdu.core.common.model.http.DpsHeaders;
+import org.opengroup.osdu.core.common.model.legal.LegalTag;
+import org.opengroup.osdu.core.common.model.legal.ListLegalTagArgs;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LegalTagRepositoryImplTest {
+
+    private static final String dataPartitionId = "data-partition-id";
+
+    @Mock
+    private CosmosStore cosmosStore;
+
+    @Mock
+    private DpsHeaders headers;
+
+    @InjectMocks
+    private LegalTagRepositoryImpl sut;
+
+    @Before
+    public void init() {
+        lenient().doReturn(dataPartitionId).when(headers).getPartitionId();
+    }
+
+    @Test(expected = AppException.class)
+    public void testCreateLegalTagAlreadyExisting_throwsException() {
+        long id = 1234;
+        String strId = String.valueOf(id);
+        LegalTag legalTag = getLegalTagWithId(id);
+        Optional<LegalTag> optionalLegalTag = Optional.of(legalTag);
+        doReturn(optionalLegalTag).when(cosmosStore).findItem(eq(dataPartitionId), any(), any(), eq(strId), eq(strId), any());
+        try {
+            sut.create(legalTag);
+        } catch (AppException e) {
+            int errorCode = 409;
+            String errorMessage = "LegalTag already exists";
+            validateAppException(e, errorCode, errorMessage);
+            throw (e);
+        }
+    }
+
+    @Test
+    public void testCreateLegalTag_upsertItem_executesCorrectQuery() {
+        long id = 1234;
+        String strId = String.valueOf(id);
+        LegalTag legalTag = getLegalTagWithId(id);
+        long obtainedId = sut.create(legalTag);
+
+        ArgumentCaptor<LegalTagDoc> arg = ArgumentCaptor.forClass(LegalTagDoc.class);
+        verify(cosmosStore).upsertItem(eq(dataPartitionId), any(), any(), arg.capture());
+
+        assertEquals(arg.getValue().getId(), strId);
+        assertEquals(obtainedId, id);
+    }
+
+    @Test
+    public void testGetLegalTagCollections_whenIdsIsNull() {
+        long[] ids = null;
+        List<LegalTag> output = (List<LegalTag>) sut.get(ids);
+        assertEquals(output.size(), 0);
+    }
+
+    @Test
+    public void testGetLegalTagCollections_whenIdsIsNotNull() {
+        long[] ids = {1234, 9876};
+        String[] strIds = Arrays.stream(ids).mapToObj(String::valueOf).toArray(String[]::new);
+        Optional[] legalTagDocs = new Optional[2];
+        legalTagDocs[0] = Optional.of(new LegalTagDoc(strIds[0], getLegalTagWithId(ids[0])));
+        legalTagDocs[1] = Optional.of(new LegalTagDoc(strIds[0], getLegalTagWithId(ids[1])));
+
+        doReturn(legalTagDocs[0]).when(cosmosStore).findItem(eq(dataPartitionId), any(), any(), eq(strIds[0]), eq(strIds[0]), any());
+        doReturn(legalTagDocs[1]).when(cosmosStore).findItem(eq(dataPartitionId), any(), any(), eq(strIds[1]), eq(strIds[1]), any());
+
+        List<LegalTag> output = (List<LegalTag>) sut.get(ids);
+        assertEquals(output.size(), 2);
+        assertEquals(output.get(0).getId().longValue(), ids[0]);
+        assertEquals(output.get(1).getId().longValue(), ids[1]);
+    }
+
+    @Test
+    public void testDeleteLegalTag_whenLegalTagDoesNotExist() {
+        long id = 1234;
+        LegalTag legalTag = getLegalTagWithId(id);
+        boolean status = sut.delete(legalTag);
+        assertEquals(status, false);
+    }
+
+    @Test
+    public void testDeleteLegalTag_whenLegalTagExists() {
+        long id = 1234;
+        String strId = String.valueOf(id);
+        LegalTag legalTag = getLegalTagWithId(id);
+        Optional<LegalTag> optionalLegalTag = Optional.of(legalTag);
+        doReturn(optionalLegalTag).when(cosmosStore).findItem(eq(dataPartitionId), any(), any(), eq(strId), eq(strId), any());
+        boolean status = sut.delete(legalTag);
+
+        ArgumentCaptor<String> arg1 = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<String> arg2 = ArgumentCaptor.forClass(String.class);
+        verify(cosmosStore).deleteItem(eq(dataPartitionId), any(), any(), arg1.capture(), arg2.capture());
+
+        assertEquals(status, true);
+        assertEquals(arg1.getValue(), strId);
+        assertEquals(arg2.getValue(), strId);
+    }
+
+    @Test
+    public void testUpdateLegalTag_whenProvidedLegalTagIsNull() {
+        LegalTag legalTag = sut.update(null);
+        assertNull(legalTag);
+    }
+
+    @Test(expected = AppException.class)
+    public void testUpdateLegalTag_whenValidItemDoesNotExist_throwsException() {
+        long id = 1234;
+        LegalTag legalTag = getLegalTagWithId(id);
+        try {
+            sut.update(legalTag);
+        } catch (AppException e) {
+            int errorCode = 404;
+            String errorMessage = "Cannot update a LegalTag that does not exist";
+            validateAppException(e, errorCode, errorMessage);
+            throw (e);
+        }
+    }
+
+    @Test
+    public void testUpdateLegalTag_whenValidItemExists() {
+        long id = 1234;
+        String strId = String.valueOf(id);
+        LegalTag legalTag = getLegalTagWithId(id);
+        Optional<LegalTag> optionalLegalTag = Optional.of(legalTag);
+        doReturn(optionalLegalTag).when(cosmosStore).findItem(eq(dataPartitionId), any(), any(), eq(strId), eq(strId), any());
+        LegalTag obtainedLegalTag = sut.update(legalTag);
+
+        ArgumentCaptor<LegalTagDoc> arg = ArgumentCaptor.forClass(LegalTagDoc.class);
+        verify(cosmosStore).upsertItem(eq(dataPartitionId), any(), any(), arg.capture());
+
+        assertEquals(arg.getValue().getId(), strId);
+        assertEquals(obtainedLegalTag.getId().longValue(), id);
+    }
+
+    @Test
+    public void testListLegalTags_queryItems_executesCorrectQuery() {
+        long[] ids = {1234, 9876};
+        String[] strIds = Arrays.stream(ids).mapToObj(String::valueOf).toArray(String[]::new);
+        List<LegalTagDoc> legalTagDocs = Arrays.asList(new LegalTagDoc(strIds[0], getLegalTagWithId(ids[0])), new LegalTagDoc(strIds[1], getLegalTagWithId(ids[1])));
+
+        ArgumentCaptor<SqlQuerySpec> query = ArgumentCaptor.forClass(SqlQuerySpec.class);
+        ArgumentCaptor<FeedOptions> feedOptions = ArgumentCaptor.forClass(FeedOptions.class);
+
+        doReturn(legalTagDocs).when(cosmosStore).queryItems(eq(dataPartitionId), any(), any(), any(), any(), any());
+        ListLegalTagArgs legalTagArgs = new ListLegalTagArgs();
+        legalTagArgs.setIsValid(true);
+        List<LegalTag> output = (List<LegalTag>) sut.list(legalTagArgs);
+
+        assertEquals(output.size(), 2);
+        assertEquals(output.get(0).getId().longValue(), ids[0]);
+        assertEquals(output.get(1).getId().longValue(), ids[1]);
+
+        verify(cosmosStore).queryItems(eq(dataPartitionId), any(), any(), query.capture(), feedOptions.capture(), any());
+        assertEquals(query.getValue().getQueryText(), "SELECT * FROM c WHERE c.legalTag.isValid = @isValid");
+        assertTrue(feedOptions.getValue().getEnableCrossPartitionQuery());
+
+        SqlParameterList parameters = query.getValue().getParameters();
+        SqlParameter isValid = parameters.get(0);
+
+        assertEquals(isValid.getName(), "@isValid");
+        assertEquals(isValid.getValue(Boolean.class), true);
+    }
+
+    private LegalTag getLegalTagWithId(long id) {
+        LegalTag legalTag = new LegalTag();
+        legalTag.setId(id);
+        return legalTag;
+    }
+
+    private void validateAppException(AppException e, int errorCode, String errorMessage) {
+        assertEquals(errorCode, e.getError().getCode());
+        assertThat(e.getError().getMessage(), containsString(errorMessage));
+    }
+}
diff --git a/provider/legal-azure/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/provider/legal-azure/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000000000000000000000000000000000..1f0955d450f0dc49ca715b1a0a88a5aa746ee11e
--- /dev/null
+++ b/provider/legal-azure/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline