Introduce read-only ContentProvider for cards (#1121)

This commit is contained in:
José Rebelo
2023-07-03 19:59:39 +01:00
committed by GitHub
parent 28c0b488e6
commit bf94d208bd
10 changed files with 595 additions and 43 deletions

View File

@@ -67,25 +67,6 @@ public class ImportExportTest {
mDatabase = TestHelpers.getEmptyDb(activity).getWritableDatabase();
}
/**
* Add the given number of cards, each with
* an index in the store name.
*
* @param cardsToAdd
*/
private void addLoyaltyCards(int cardsToAdd) {
// Add in reverse order to test sorting
for (int index = cardsToAdd; index > 0; index--) {
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
long id = DBHelper.insertLoyaltyCard(mDatabase, storeName, note, null, null, new BigDecimal(String.valueOf(index)), null, BARCODE_DATA, null, BARCODE_TYPE, index, 0, null,0);
boolean result = (id != -1);
assertTrue(result);
}
assertEquals(cardsToAdd, DBHelper.getLoyaltyCardCount(mDatabase));
}
private void addLoyaltyCardsFiveStarred() {
int cardsToAdd = 9;
// Add in reverse order to test sorting
@@ -183,18 +164,6 @@ public class ImportExportTest {
assertEquals(4, DBHelper.getLoyaltyCardCount(mDatabase));
}
private void addGroups(int groupsToAdd) {
// Add in reverse order to test sorting
for (int index = groupsToAdd; index > 0; index--) {
String groupName = String.format("group, \"%4d", index);
long id = DBHelper.insertGroup(mDatabase, groupName);
boolean result = (id != -1);
assertTrue(result);
}
assertEquals(groupsToAdd, DBHelper.getGroupCount(mDatabase));
}
/**
* Check that all of the cards follow the pattern
* specified in addLoyaltyCards(), and are in sequential order
@@ -285,7 +254,7 @@ public class ImportExportTest {
/**
* Check that all of the groups follow the pattern
* specified in addGroups(), and are in sequential order
* specified in {@link TestHelpers#addGroups}, and are in sequential order
* where the smallest group's index is 1
*/
private void checkGroups() {
@@ -308,7 +277,7 @@ public class ImportExportTest {
public void multipleCardsExportImport() throws IOException {
final int NUM_CARDS = 10;
addLoyaltyCards(NUM_CARDS);
TestHelpers.addLoyaltyCards(mDatabase, NUM_CARDS);
ByteArrayOutputStream outData = new ByteArrayOutputStream();
OutputStreamWriter outStream = new OutputStreamWriter(outData);
@@ -338,7 +307,7 @@ public class ImportExportTest {
final int NUM_CARDS = 10;
List<char[]> passwords = Arrays.asList(null, "123456789".toCharArray());
for (char[] password : passwords) {
addLoyaltyCards(NUM_CARDS);
TestHelpers.addLoyaltyCards(mDatabase, NUM_CARDS);
ByteArrayOutputStream outData = new ByteArrayOutputStream();
OutputStreamWriter outStream = new OutputStreamWriter(outData);
@@ -411,8 +380,8 @@ public class ImportExportTest {
final int NUM_CARDS = 10;
final int NUM_GROUPS = 3;
addLoyaltyCards(NUM_CARDS);
addGroups(NUM_GROUPS);
TestHelpers.addLoyaltyCards(mDatabase, NUM_CARDS);
TestHelpers.addGroups(mDatabase, NUM_GROUPS);
List<Group> emptyGroup = new ArrayList<>();
@@ -484,7 +453,7 @@ public class ImportExportTest {
public void importExistingCardsNotReplace() throws IOException {
final int NUM_CARDS = 10;
addLoyaltyCards(NUM_CARDS);
TestHelpers.addLoyaltyCards(mDatabase, NUM_CARDS);
ByteArrayOutputStream outData = new ByteArrayOutputStream();
OutputStreamWriter outStream = new OutputStreamWriter(outData);
@@ -513,7 +482,7 @@ public class ImportExportTest {
final int NUM_CARDS = 10;
for (DataFormat format : DataFormat.values()) {
addLoyaltyCards(NUM_CARDS);
TestHelpers.addLoyaltyCards(mDatabase, NUM_CARDS);
ByteArrayOutputStream outData = new ByteArrayOutputStream();
OutputStreamWriter outStream = new OutputStreamWriter(outData);
@@ -558,7 +527,7 @@ public class ImportExportTest {
final File sdcardDir = Environment.getExternalStorageDirectory();
final File exportFile = new File(sdcardDir, "Catima.csv");
addLoyaltyCards(NUM_CARDS);
TestHelpers.addLoyaltyCards(mDatabase, NUM_CARDS);
TestTaskCompleteListener listener = new TestTaskCompleteListener();

View File

@@ -1,14 +1,23 @@
package protect.card_locker;
import android.app.Activity;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.google.zxing.BarcodeFormat;
import java.io.FileNotFoundException;
import java.math.BigDecimal;
public class TestHelpers {
static public DBHelper getEmptyDb(Activity activity) {
DBHelper db = new DBHelper(activity);
private static final String BARCODE_DATA = "428311627547";
private static final CatimaBarcode BARCODE_TYPE = CatimaBarcode.fromBarcode(BarcodeFormat.UPC_A);
public static DBHelper getEmptyDb(Context context) {
DBHelper db = new DBHelper(context);
SQLiteDatabase database = db.getWritableDatabase();
// Make sure no files remain
@@ -19,7 +28,7 @@ public class TestHelpers {
for (ImageLocationType imageLocationType : ImageLocationType.values()) {
try {
Utils.saveCardImage(activity.getApplicationContext(), null, cardID, imageLocationType);
Utils.saveCardImage(context.getApplicationContext(), null, cardID, imageLocationType);
} catch (FileNotFoundException ignored) {
}
}
@@ -34,4 +43,35 @@ public class TestHelpers {
return db;
}
/**
* Add the given number of cards, each with an index in the store name.
*
* @param mDatabase
* @param cardsToAdd
*/
public static void addLoyaltyCards(final SQLiteDatabase mDatabase, final int cardsToAdd) {
// Add in reverse order to test sorting
for (int index = cardsToAdd; index > 0; index--) {
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
long id = DBHelper.insertLoyaltyCard(mDatabase, storeName, note, null, null, new BigDecimal(String.valueOf(index)), null, BARCODE_DATA, null, BARCODE_TYPE, index, 0, null,0);
boolean result = (id != -1);
assertTrue(result);
}
assertEquals(cardsToAdd, DBHelper.getLoyaltyCardCount(mDatabase));
}
public static void addGroups(final SQLiteDatabase mDatabase, int groupsToAdd) {
// Add in reverse order to test sorting
for (int index = groupsToAdd; index > 0; index--) {
String groupName = String.format("group, \"%4d", index);
long id = DBHelper.insertGroup(mDatabase, groupName);
boolean result = (id != -1);
assertTrue(result);
}
assertEquals(groupsToAdd, DBHelper.getGroupCount(mDatabase));
}
}

View File

@@ -0,0 +1,259 @@
package protect.card_locker.contentprovider;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.Group;
import protect.card_locker.TestHelpers;
@RunWith(RobolectricTestRunner.class)
public class CardsContentProviderTest {
private ContentResolver mResolver;
private DBHelper dbHelper;
private SQLiteDatabase mDatabase;
@Before
public void setUp() {
final ContentProvider contentProvider = new CardsContentProvider();
final ProviderInfo providerInfo = new ProviderInfo();
providerInfo.authority = CardsContentProvider.AUTHORITY;
contentProvider.attachInfo(RuntimeEnvironment.getApplication(), providerInfo);
contentProvider.onCreate();
Robolectric.buildContentProvider(CardsContentProvider.class).create(providerInfo);
mResolver = RuntimeEnvironment.getApplication().getContentResolver();
dbHelper = TestHelpers.getEmptyDb(RuntimeEnvironment.getApplication());
mDatabase = dbHelper.getWritableDatabase();
}
@After
public void cleanup() {
mDatabase.close();
dbHelper.close();
}
@Test
public void testVersion() {
final Uri versionUri = getUri("version");
try (Cursor cursor = mResolver.query(versionUri, null, null, null)) {
assertEquals("number of entries", 1, cursor.getCount());
assertEquals("number of columns", 2, cursor.getColumnCount());
assertArrayEquals("column names", new String[]{"major", "minor"}, cursor.getColumnNames());
cursor.moveToNext();
assertEquals("major version", 1, cursor.getInt(cursor.getColumnIndexOrThrow("major")));
assertEquals("minor version", 0, cursor.getInt(cursor.getColumnIndexOrThrow("minor")));
}
}
@Test
public void testCards() {
final Uri cardsUri = getUri("cards");
try (Cursor cursor = mResolver.query(cardsUri, null, null, null)) {
assertEquals(cursor.getCount(), 0);
}
final String store = "the best store";
final String note = "this is a note";
final Date validFrom = Date.from(Instant.ofEpochMilli(1687112209000L));
final Date expiry = Date.from(Instant.ofEpochMilli(1687112277000L));
final BigDecimal balance = new BigDecimal("123.20");
final Currency balanceType = Currency.getInstance("EUR");
final String cardId = "a-card-id";
final String barcodeId = "barcode-id";
final CatimaBarcode barcodeType = CatimaBarcode.fromName("QR_CODE");
final int headerColor = 0xFFFF00FF;
final int starStatus = 1;
final long lastUsed = 1687112282000L;
final int archiveStatus = 1;
long id = DBHelper.insertLoyaltyCard(
mDatabase, store, note, validFrom, expiry, balance, balanceType,
cardId, barcodeId, barcodeType, headerColor, starStatus, lastUsed,
archiveStatus
);
assertEquals("expect first card", 1, id);
try (Cursor cursor = mResolver.query(cardsUri, null, null, null)) {
assertEquals("number of cards", 1, cursor.getCount());
final String[] expectedColumns = new String[]{
"_id", "store", "validfrom", "expiry", "balance", "balancetype",
"note", "headercolor", "cardid", "barcodeid",
"barcodetype", "starstatus", "lastused", "archive"
};
assertEquals("number of columns", expectedColumns.length, cursor.getColumnCount());
assertEquals(
"column names",
new HashSet<>(Arrays.asList(expectedColumns)),
new HashSet<>(Arrays.asList(cursor.getColumnNames()))
);
cursor.moveToNext();
final int actualId = cursor.getInt(cursor.getColumnIndexOrThrow("_id"));
final String actualName = cursor.getString(cursor.getColumnIndexOrThrow("store"));
final String actualNote = cursor.getString(cursor.getColumnIndexOrThrow("note"));
final long actualValidFrom = cursor.getLong(cursor.getColumnIndexOrThrow("validfrom"));
final long actualExpiry = cursor.getLong(cursor.getColumnIndexOrThrow("expiry"));
final BigDecimal actualBalance = new BigDecimal(cursor.getString(cursor.getColumnIndexOrThrow("balance")));
final String actualBalanceType = cursor.getString(cursor.getColumnIndexOrThrow("balancetype"));
final String actualCardId = cursor.getString(cursor.getColumnIndexOrThrow("cardid"));
final String actualBarcodeId = cursor.getString(cursor.getColumnIndexOrThrow("barcodeid"));
final String actualBarcodeType = cursor.getString(cursor.getColumnIndexOrThrow("barcodetype"));
final int actualHeaderColor = cursor.getInt(cursor.getColumnIndexOrThrow("headercolor"));
final int actualStarred = cursor.getInt(cursor.getColumnIndexOrThrow("starstatus"));
final long actualLastUsed = cursor.getLong(cursor.getColumnIndexOrThrow("lastused"));
final int actualArchiveStatus = cursor.getInt(cursor.getColumnIndexOrThrow("archive"));
assertEquals("Id", 1, actualId);
assertEquals("Name", store, actualName);
assertEquals("Note", note, actualNote);
assertEquals("ValidFrom", validFrom.getTime(), actualValidFrom);
assertEquals("Expiry", expiry.getTime(), actualExpiry);
assertEquals("Balance", balance, actualBalance);
assertEquals("BalanceTypeColumn", balanceType.toString(), actualBalanceType);
assertEquals("CardId", cardId, actualCardId);
assertEquals("BarcodeId", barcodeId, actualBarcodeId);
assertEquals("BarcodeType", barcodeType.format().name(), actualBarcodeType);
assertEquals("HeaderColorColumn", headerColor, actualHeaderColor);
assertEquals("Starred", starStatus, actualStarred);
assertEquals("LastUsed", lastUsed, actualLastUsed);
assertEquals("ArchiveStatus", archiveStatus, actualArchiveStatus);
}
}
@Test
public void testCardsProjection() {
final Uri cardsUri = getUri("cards");
try (Cursor cursor = mResolver.query(cardsUri, null, null, null)) {
assertEquals(cursor.getCount(), 0);
}
TestHelpers.addLoyaltyCards(mDatabase, 1);
// Query with projection of columns, including internal column names, which should be filtered out
try (Cursor cursor = mResolver.query(cardsUri, new String[] {"_id", "store", "zoomlevel"}, null, null)) {
assertEquals("number of cards", 1, cursor.getCount());
assertEquals("number of columns", 2, cursor.getColumnCount());
assertArrayEquals("column names", new String[]{"_id", "store"}, cursor.getColumnNames());
cursor.moveToNext();
final int actualId = cursor.getInt(cursor.getColumnIndexOrThrow("_id"));
final String actualName = cursor.getString(cursor.getColumnIndexOrThrow("store"));
assertEquals("id", 1, actualId);
assertEquals("store", "store, \" 1", actualName);
}
}
@Test
public void testGroups() {
final Uri groupsUri = getUri("groups");
try (Cursor cursor = mResolver.query(groupsUri, null, null, null)) {
assertEquals("start without groups", 0, cursor.getCount());
}
TestHelpers.addGroups(mDatabase, 4);
try (Cursor cursor = mResolver.query(groupsUri, null, null, null)) {
assertEquals("number of groups", 4, cursor.getCount());
assertEquals("number of columns", 2, cursor.getColumnCount());
assertArrayEquals("column names", new String[]{"_id", "orderId"}, cursor.getColumnNames());
for (int i = 0; i < 4; i++) {
cursor.moveToNext();
assertEquals(
String.format("groups[%d]._id", i),
String.format("group, \"%4d", 4 - i),
cursor.getString(cursor.getColumnIndexOrThrow("_id"))
);
assertEquals(
String.format("groups[%d].orderId", i),
String.valueOf(i),
cursor.getString(cursor.getColumnIndexOrThrow("orderId"))
);
}
}
}
@Test
public void testCardGroups() {
final Uri cardGroupsUri = getUri("card_groups");
try (Cursor cursor = mResolver.query(cardGroupsUri, null, null, null)) {
assertEquals(cursor.getCount(), 0);
}
TestHelpers.addLoyaltyCards(mDatabase, 5);
TestHelpers.addGroups(mDatabase, 4);
final List<Group> groupsForOne = new ArrayList<>();
groupsForOne.add(DBHelper.getGroup(mDatabase, "group, \" 1"));
final List<Group> groupsForTwo = new ArrayList<>();
groupsForTwo.add(DBHelper.getGroup(mDatabase, "group, \" 1"));
groupsForTwo.add(DBHelper.getGroup(mDatabase, "group, \" 2"));
DBHelper.setLoyaltyCardGroups(mDatabase, 1, groupsForOne);
DBHelper.setLoyaltyCardGroups(mDatabase, 2, groupsForTwo);
final Map<String, List<String>> expectedGroups = new HashMap<>() {{
put("group, \" 1", Arrays.asList("1", "2"));
put("group, \" 2", Collections.singletonList("2"));
}};
try (Cursor cursor = mResolver.query(cardGroupsUri, null, null, null)) {
assertEquals("number of card groups", 3, cursor.getCount());
assertEquals("number of columns", 2, cursor.getColumnCount());
assertArrayEquals("column names", new String[]{"cardId", "groupId"}, cursor.getColumnNames());
final Map<String, List<String>> groups = new HashMap<>();
while (cursor.moveToNext()) {
final String cardId = cursor.getString(cursor.getColumnIndexOrThrow("cardId"));
final String groupId = cursor.getString(cursor.getColumnIndexOrThrow("groupId"));
groups.computeIfAbsent(groupId, k -> new ArrayList<>()).add(cardId);
}
assertEquals("expected groups with cards", expectedGroups, groups);
}
}
private Uri getUri(final String endpoint) {
return Uri.parse(String.format(Locale.ROOT, "content://%s/%s", CardsContentProvider.AUTHORITY, endpoint));
}
}