1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16package com.android.providers.calendar; 17 18import android.content.ContentValues; 19import android.content.Context; 20import android.database.Cursor; 21import android.database.DatabaseUtils; 22import android.database.sqlite.SQLiteDatabase; 23import android.database.sqlite.SQLiteDatabase.CursorFactory; 24import android.provider.Calendar; 25import android.test.suitebuilder.annotation.MediumTest; 26import android.text.TextUtils; 27import android.util.Log; 28 29import junit.framework.TestCase; 30 31public class CalendarDatabaseHelperTest extends TestCase { 32 33 private SQLiteDatabase mBadDb; 34 private SQLiteDatabase mGoodDb; 35 private DatabaseUtils.InsertHelper mBadEventsInserter; 36 private DatabaseUtils.InsertHelper mGoodEventsInserter; 37 38 @Override 39 public void setUp() { 40 mBadDb = SQLiteDatabase.create(null); 41 assertNotNull(mBadDb); 42 mGoodDb = SQLiteDatabase.create(null); 43 assertNotNull(mGoodDb); 44 } 45 46 private void createVersion67EventsTable(SQLiteDatabase db) { 47 db.execSQL("CREATE TABLE Events (" + 48 "_id INTEGER PRIMARY KEY," + 49 "_sync_account TEXT," + 50 "_sync_account_type TEXT," + 51 "_sync_id TEXT," + 52 "_sync_version TEXT," + 53 "_sync_time TEXT," + // UTC 54 "_sync_local_id INTEGER," + 55 "_sync_dirty INTEGER," + 56 "_sync_mark INTEGER," + // To filter out new rows 57 "calendar_id INTEGER NOT NULL," + 58 "htmlUri TEXT," + 59 "title TEXT," + 60 "eventLocation TEXT," + 61 "description TEXT," + 62 "eventStatus INTEGER," + 63 "selfAttendeeStatus INTEGER NOT NULL DEFAULT 0," + 64 "commentsUri TEXT," + 65 "dtstart INTEGER," + // millis since epoch 66 "dtend INTEGER," + // millis since epoch 67 "eventTimezone TEXT," + // timezone for event 68 "duration TEXT," + 69 "allDay INTEGER NOT NULL DEFAULT 0," + 70 "visibility INTEGER NOT NULL DEFAULT 0," + 71 "transparency INTEGER NOT NULL DEFAULT 0," + 72 "hasAlarm INTEGER NOT NULL DEFAULT 0," + 73 "hasExtendedProperties INTEGER NOT NULL DEFAULT 0," + 74 "rrule TEXT," + 75 "rdate TEXT," + 76 "exrule TEXT," + 77 "exdate TEXT," + 78 "originalEvent TEXT," + // _sync_id of recurring event 79 "originalInstanceTime INTEGER," + // millis since epoch 80 "originalAllDay INTEGER," + 81 "lastDate INTEGER," + // millis since epoch 82 "hasAttendeeData INTEGER NOT NULL DEFAULT 0," + 83 "guestsCanModify INTEGER NOT NULL DEFAULT 0," + 84 "guestsCanInviteOthers INTEGER NOT NULL DEFAULT 1," + 85 "guestsCanSeeGuests INTEGER NOT NULL DEFAULT 1," + 86 "organizer STRING," + 87 "deleted INTEGER NOT NULL DEFAULT 0," + 88 "dtstart2 INTEGER," + //millis since epoch, allDay events in local timezone 89 "dtend2 INTEGER," + //millis since epoch, allDay events in local timezone 90 "eventTimezone2 TEXT," + //timezone for event with allDay events in local timezone 91 "syncAdapterData TEXT" + //available for use by sync adapters 92 ");"); 93 } 94 95 private void addVersion67Events() { 96 // April 5th 1:01:01 AM to April 6th 1:01:01 97 mBadDb.execSQL("INSERT INTO Events (_id,dtstart,dtend,duration,dtstart2,dtend2," + 98 "eventTimezone,eventTimezone2,allDay,calendar_id) " + 99 "VALUES (1,1270454471000,1270540872000,'P10S'," + 100 "1270454460000,1270540861000,'America/Los_Angeles','America/Los_Angeles',1,1);"); 101 102 // April 5th midnight to April 6th midnight, duration cleared 103 mGoodDb.execSQL("INSERT INTO Events (_id,dtstart,dtend,duration,dtstart2,dtend2," + 104 "eventTimezone,eventTimezone2,allDay,calendar_id) " + 105 "VALUES (1,1270425600000,1270512000000,null," + 106 "1270450800000,1270537200000,'UTC','America/Los_Angeles',1,1);"); 107 108 // April 5th 1:01:01 AM to April 6th 1:01:01, recurring weekly (We only check for the 109 // existence of an rrule so it doesn't matter if the day is correct) 110 mBadDb.execSQL("INSERT INTO Events (_id,dtstart,dtend,duration,dtstart2,dtend2," + 111 "eventTimezone,eventTimezone2,allDay,rrule,calendar_id) " + 112 "VALUES (2,1270454462000,1270540863000," + 113 "'P10S',1270454461000,1270540861000,'America/Los_Angeles','America/Los_Angeles',1," + 114 "'WEEKLY:MON',1);"); 115 116 // April 5th midnight with 1 day duration, if only dtend was wrong we wouldn't fix it, but 117 // if anything else is wrong we clear dtend to be sure. 118 mGoodDb.execSQL("INSERT INTO Events (" + 119 "_id,dtstart,dtend,duration,dtstart2,dtend2," + 120 "eventTimezone,eventTimezone2,allDay,rrule,calendar_id)" + 121 "VALUES (2,1270425600000,null,'P1D',1270450800000,null," + 122 "'UTC','America/Los_Angeles',1," + 123 "'WEEKLY:MON',1);"); 124 125 assertEquals(mBadDb.rawQuery("SELECT _id FROM Events;", null).getCount(), 2); 126 assertEquals(mGoodDb.rawQuery("SELECT _id FROM Events;", null).getCount(), 2); 127 } 128 129 @MediumTest 130 public void testUpgradeToVersion69() { 131 // Create event tables 132 createVersion67EventsTable(mBadDb); 133 createVersion67EventsTable(mGoodDb); 134 // Fill in good and bad events 135 addVersion67Events(); 136 // Run the upgrade on the bad events 137 CalendarDatabaseHelper.upgradeToVersion69(mBadDb); 138 Cursor badCursor = null; 139 Cursor goodCursor = null; 140 try { 141 badCursor = mBadDb.rawQuery("SELECT _id,dtstart,dtend,duration,dtstart2,dtend2," + 142 "eventTimezone,eventTimezone2,rrule FROM Events WHERE allDay=?", 143 new String[] {"1"}); 144 goodCursor = mGoodDb.rawQuery("SELECT _id,dtstart,dtend,duration,dtstart2,dtend2," + 145 "eventTimezone,eventTimezone2,rrule FROM Events WHERE allDay=?", 146 new String[] {"1"}); 147 // Check that we get the correct results back 148 assertTrue(compareCursors(badCursor, goodCursor)); 149 } finally { 150 if (badCursor != null) { 151 badCursor.close(); 152 } 153 if (goodCursor != null) { 154 goodCursor.close(); 155 } 156 } 157 } 158 159 /** 160 * Compares two cursors to see if they contain the same data. 161 * 162 * @return Returns true of the cursors contain the same data and are not null, false 163 * otherwise 164 */ 165 private static boolean compareCursors(Cursor c1, Cursor c2) { 166 if(c1 == null || c2 == null) { 167 Log.d("CDBT","c1 is " + c1 + " and c2 is " + c2); 168 return false; 169 } 170 171 int numColumns = c1.getColumnCount(); 172 if (numColumns != c2.getColumnCount()) { 173 Log.d("CDBT","c1 has " + numColumns + " columns and c2 has " + c2.getColumnCount()); 174 return false; 175 } 176 177 if (c1.getCount() != c2.getCount()) { 178 Log.d("CDBT","c1 has " + c1.getCount() + " rows and c2 has " + c2.getCount()); 179 return false; 180 } 181 182 c1.moveToPosition(-1); 183 c2.moveToPosition(-1); 184 while(c1.moveToNext() && c2.moveToNext()) { 185 for(int i = 0; i < numColumns; i++) { 186 if(!TextUtils.equals(c1.getString(i),c2.getString(i))) { 187 Log.d("CDBT", c1.getString(i) + "\n" + c2.getString(i)); 188 return false; 189 } 190 } 191 } 192 193 return true; 194 } 195} 196