HealthConnect Database Backward Compatibility tests

Backward compatibility tests are written to ensure the backward
compatibility of Healthconnect database.

Test: atest HealthConnectDatabaseBackwardCompatibilityTest.java

Bug: b/284246445
Change-Id: I0e88da1fafae2fc409d563b7ed2aaee88794a2d8
diff --git a/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/database/DataBaseTestUtils.java b/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/database/DatabaseTestUtils.java
similarity index 97%
rename from tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/database/DataBaseTestUtils.java
rename to tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/database/DatabaseTestUtils.java
index a5bb84f..e7020d8 100644
--- a/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/database/DataBaseTestUtils.java
+++ b/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/database/DatabaseTestUtils.java
@@ -38,7 +38,7 @@
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
-public class DataBaseTestUtils {
+public class DatabaseTestUtils {
     public static final String INSTALL_ARG_FORCE_QUERYABLE = "--force-queryable";
 
     public static final String HC_APEX_RESOURCE_PATH_PREFIX = "/HealthConnectApexFiles/";
@@ -123,7 +123,7 @@
         }
         File tempDir = FileUtil.createTempDir("HcHostSideTests");
         File file = new File(tempDir, filenameInResources);
-        InputStream in = DataBaseTestUtils.class.getResourceAsStream(fullResourceName);
+        InputStream in = DatabaseTestUtils.class.getResourceAsStream(fullResourceName);
         if (in == null) {
             throw new IllegalArgumentException("Resource not found: " + fullResourceName);
         }
@@ -384,7 +384,7 @@
                                 mTableListCurrentVersion
                                         .get(referTableName)
                                         .getColumnInfoMapping()
-                                        .get(foreignKeyInfo.getForeignKeyName())
+                                        .get(foreignKeyInfo.getForeignKeyReferredColumnName())
                                         .getConstraints();
                         if (!mTableListCurrentVersion
                                         .get(referTableName)
@@ -472,7 +472,7 @@
                             mTableListCurrentVersion
                                     .get(referTableName)
                                     .getColumnInfoMapping()
-                                    .get(foreignKeyInfo.getForeignKeyName())
+                                    .get(foreignKeyInfo.getForeignKeyReferredColumnName())
                                     .getConstraints();
                     /**
                      * Checking whether the column to which foreign key has been mapped is primary
@@ -498,5 +498,14 @@
             }
         }
     }
+
+    /**
+     * @return true if apex version file is present in resources otherwise false.
+     */
+    public static boolean isFilePresentInResources(String filenameInResources) {
+        final String fullResourceName = HC_APEX_RESOURCE_PATH_PREFIX + filenameInResources;
+        InputStream in = DatabaseTestUtils.class.getResourceAsStream(fullResourceName);
+        return in != null;
+    }
 }
 
diff --git a/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/database/HealthConnectDatabaseBackwardCompatibilityTest.java b/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/database/HealthConnectDatabaseBackwardCompatibilityTest.java
new file mode 100644
index 0000000..bade0c5
--- /dev/null
+++ b/tests/cts/hostsidetests/healthconnect/host/src/android/healthconnect/cts/database/HealthConnectDatabaseBackwardCompatibilityTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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 android.healthconnect.cts.database;
+
+import static android.healthconnect.cts.database.DatabaseTestUtils.assertInstallSucceeds;
+import static android.healthconnect.cts.database.DatabaseTestUtils.checkColumnModification;
+import static android.healthconnect.cts.database.DatabaseTestUtils.checkExistingTableDeletion;
+import static android.healthconnect.cts.database.DatabaseTestUtils.checkIndexModification;
+import static android.healthconnect.cts.database.DatabaseTestUtils.deleteHcDatabase;
+import static android.healthconnect.cts.database.DatabaseTestUtils.getCurrentHcDatabaseVersion;
+import static android.healthconnect.cts.database.DatabaseTestUtils.isFilePresentInResources;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * HealthConnectDatabaseBackwardCompatibilityTest contains test cases that ensures backward
+ * compatibility of the HealthConnect Database.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class HealthConnectDatabaseBackwardCompatibilityTest extends BaseHostJUnit4Test {
+    public static final String APEX_FILE_PREFIX = "HealthConnectVersion_";
+    public static final String APEX_FILE_SUFFIX = ".apex";
+    public static String sCurrentVersionFile;
+    public static String sPreviousVersionFile;
+    public static HashMap<String, TableInfo> sPreviousVersionSchema = new HashMap<>();
+    public static HashMap<String, TableInfo> sCurrentVersionSchema = new HashMap<>();
+
+    @Before
+    /** Initial setUp to get the Schema Hashmaps of current and previous versions. */
+    public void setUp() throws Exception {
+        /** check for device availability. */
+        ITestDevice device = getDevice();
+        assertThat(device).isNotNull();
+        rebootAndEnableRoot();
+
+        /**
+         * Getting the current version of HealthConnect database and setting the current and
+         * previous version file names.
+         */
+        int currentVersion = getCurrentHcDatabaseVersion(device);
+        setVersionFileName(currentVersion);
+    }
+
+    public void setVersionFileName(int currentVersion) {
+        sCurrentVersionFile = APEX_FILE_PREFIX + currentVersion + APEX_FILE_SUFFIX;
+        sPreviousVersionFile = APEX_FILE_PREFIX + (currentVersion - 1) + APEX_FILE_SUFFIX;
+    }
+
+    /**
+     * @return schema of the version that is installed on device.
+     */
+    public static String getSchema(ITestDevice device) throws DeviceNotAvailableException {
+        return device.executeShellCommand(
+                "cd ~; sqlite3 data/system_ce/0/healthconnect/healthconnect.db" + " \".schema\"");
+    }
+
+    private void rebootAndEnableRoot() throws DeviceNotAvailableException {
+        getDevice().reboot();
+        getDevice().waitForDeviceAvailable();
+        /** Enable root for device. */
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+    }
+
+    /** test to check the backward compatibility of database versions. */
+    @Test
+    public void checkBackwardCompatibility() throws Exception {
+        /** Checking for the availability of both version files in resources/ */
+        Assume.assumeTrue(
+                isFilePresentInResources(sCurrentVersionFile)
+                        && isFilePresentInResources(sPreviousVersionFile));
+
+        /** Getting the schema for current version and populating its hashmap. */
+        String currentVersionSchema = getSchema(getDevice());
+        HealthConnectDatabaseSchema currentSchema =
+                new HealthConnectDatabaseSchema(currentVersionSchema);
+        sCurrentVersionSchema = currentSchema.getTableInfo();
+
+        /** Deleting the current version database from device. */
+        deleteHcDatabase(getDevice());
+
+        /** Previous version installation */
+        assertInstallSucceeds(getDevice(), sPreviousVersionFile, true);
+        rebootAndEnableRoot();
+
+        /** Getting the schema for previous version and populating its hashmap. */
+        String previousVersionSchema = getSchema(getDevice());
+        HealthConnectDatabaseSchema previousSchema =
+                new HealthConnectDatabaseSchema(previousVersionSchema);
+        sPreviousVersionSchema = previousSchema.getTableInfo();
+
+        List<String> incompatibleChanges = new ArrayList<>();
+
+        checkTableDeletion(incompatibleChanges);
+        checkColumnModifications(incompatibleChanges);
+        checkIndexModifications(incompatibleChanges);
+
+        Assert.assertTrue(
+                "Changes made to the database are backward incompatible"
+                        + "\n"
+                        + incompatibleChanges,
+                incompatibleChanges.isEmpty());
+    }
+
+    /** Checks for the deletion of existing tables. */
+    public void checkTableDeletion(List<String> incompatibleChanges) {
+
+        List<String> backwardIncompatibleChangeList = new ArrayList<>();
+        checkExistingTableDeletion(
+                sPreviousVersionSchema, sCurrentVersionSchema, backwardIncompatibleChangeList);
+
+        if (!backwardIncompatibleChangeList.isEmpty()) {
+            incompatibleChanges.add(
+                    "Deletion of existing table is not allowed"
+                            + "\n"
+                            + backwardIncompatibleChangeList
+                            + "\n");
+        }
+    }
+
+    /** Checks for the modification in columns of existing tables. */
+    public void checkColumnModifications(List<String> incompatibleChanges) {
+
+        List<String> backwardIncompatibleChangeList = new ArrayList<>();
+        checkColumnModification(
+                sPreviousVersionSchema, sCurrentVersionSchema, backwardIncompatibleChangeList);
+
+        if (!backwardIncompatibleChangeList.isEmpty()) {
+            incompatibleChanges.add(
+                    "Changes made to the columns are backward incompatible"
+                            + "\n"
+                            + backwardIncompatibleChangeList
+                            + "\n");
+        }
+    }
+
+    /** Checks for the modifications in the indexes of existing tables. */
+    public void checkIndexModifications(List<String> incompatibleChanges) {
+
+        List<String> backwardIncompatibleChangeList = new ArrayList<>();
+        checkIndexModification(
+                sPreviousVersionSchema, sCurrentVersionSchema, backwardIncompatibleChangeList);
+
+        if (!backwardIncompatibleChangeList.isEmpty()) {
+            incompatibleChanges.add(
+                    "Changes made to the indexes are backward incompatible"
+                            + "\n"
+                            + backwardIncompatibleChangeList
+                            + "\n");
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+
+        if (sCurrentVersionFile != null && isFilePresentInResources(sCurrentVersionFile)) {
+            /** Deleting the previous version database from device. */
+            deleteHcDatabase(getDevice());
+
+            /** Current version re-installation */
+            assertInstallSucceeds(getDevice(), sCurrentVersionFile, true);
+            rebootAndEnableRoot();
+        }
+        getDevice().disableAdbRoot();
+    }
+}