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  */
16 
17 package com.example.android.notepad;
18 
19 import android.content.ContentUris;
20 import android.content.ContentValues;
21 import android.content.res.AssetFileDescriptor;
22 import android.database.Cursor;
23 import android.database.sqlite.SQLiteDatabase;
24 import android.net.Uri;
25 import android.os.ParcelFileDescriptor;
26 import android.test.ProviderTestCase2;
27 import android.test.mock.MockContentResolver;
28 
29 import java.io.BufferedReader;
30 import java.io.FileDescriptor;
31 import java.io.FileNotFoundException;
32 import java.io.FileReader;
33 import java.io.IOException;
34 import java.util.Calendar;
35 import java.util.GregorianCalendar;
36 
37 /*
38  */
39 /**
40  * This class tests the content provider for the Note Pad sample application.
41  *
42  * To learn how to run an entire test package or one of its classes, please see
43  * "Testing in Eclipse, with ADT" or "Testing in Other IDEs" in the Developer Guide.
44  */
45 public class NotePadProviderTest extends ProviderTestCase2<NotePadProvider> {
46 
47     // A URI that the provider does not offer, for testing error handling.
48     private static final Uri INVALID_URI =
49         Uri.withAppendedPath(NotePad.Notes.CONTENT_URI, "invalid");
50 
51     // Contains a reference to the mocked content resolver for the provider under test.
52     private MockContentResolver mMockResolver;
53 
54     // Contains an SQLite database, used as test data
55     private SQLiteDatabase mDb;
56 
57     // Contains the test data, as an array of NoteInfo instances.
58     private final NoteInfo[] TEST_NOTES = {
59         new NoteInfo("Note0", "This is note 0"),
60         new NoteInfo("Note1", "This is note 1"),
61         new NoteInfo("Note2", "This is note 2"),
62         new NoteInfo("Note3", "This is note 3"),
63         new NoteInfo("Note4", "This is note 4"),
64         new NoteInfo("Note5", "This is note 5"),
65         new NoteInfo("Note6", "This is note 6"),
66         new NoteInfo("Note7", "This is note 7"),
67         new NoteInfo("Note8", "This is note 8"),
68         new NoteInfo("Note9", "This is note 9") };
69 
70     // Number of milliseconds in one day (milliseconds * seconds * minutes * hours)
71     private static final long ONE_DAY_MILLIS = 1000 * 60 * 60 * 24;
72 
73     // Number of milliseconds in one week
74     private static final long ONE_WEEK_MILLIS = ONE_DAY_MILLIS * 7;
75 
76     // Creates a calendar object equal to January 1, 2010 at 12 midnight
77     private static final GregorianCalendar TEST_CALENDAR =
78         new GregorianCalendar(2010, Calendar.JANUARY, 1, 0, 0, 0);
79 
80     // Stores a timestamp value, set to an arbitrary starting point
81     private final static long START_DATE = TEST_CALENDAR.getTimeInMillis();
82 
83     // Sets a MIME type filter, used to test provider methods that return more than one MIME type
84     // for a particular note. The filter will retrieve any MIME types supported for the content URI.
85     private final static String MIME_TYPES_ALL = "*/*";
86 
87     // Sets a MIME type filter, used to test provider methods that return more than one MIME type
88     // for a particular note. The filter is nonsense, so it will not retrieve any MIME types.
89     private final static String MIME_TYPES_NONE = "qwer/qwer";
90 
91     // Sets a MIME type filter for plain text, used to the provider's methods that only handle
92     // plain text
93     private final static String MIME_TYPE_TEXT = "text/plain";
94 
95     /*
96      * Constructor for the test case class.
97      * Calls the super constructor with the class name of the provider under test and the
98      * authority name of the provider.
99      */
NotePadProviderTest()100     public NotePadProviderTest() {
101         super(NotePadProvider.class, NotePad.AUTHORITY);
102     }
103 
104     /*
105      * Sets up the test environment before each test method. Creates a mock content resolver,
106      * gets the provider under test, and creates a new database for the provider.
107      */
108     @Override
setUp()109     protected void setUp() throws Exception {
110         // Calls the base class implementation of this method.
111         super.setUp();
112 
113         // Gets the resolver for this test.
114         mMockResolver = getMockContentResolver();
115 
116         /*
117          * Gets a handle to the database underlying the provider. Gets the provider instance
118          * created in super.setUp(), gets the DatabaseOpenHelper for the provider, and gets
119          * a database object from the helper.
120          */
121         mDb = getProvider().getOpenHelperForTest().getWritableDatabase();
122     }
123 
124     /*
125      *  This method is called after each test method, to clean up the current fixture. Since
126      *  this sample test case runs in an isolated context, no cleanup is necessary.
127      */
128     @Override
tearDown()129     protected void tearDown() throws Exception {
130         super.tearDown();
131     }
132 
133     /*
134      * Sets up test data.
135      * The test data is in an SQL database. It is created in setUp() without any data,
136      * and populated in insertData if necessary.
137      */
insertData()138     private void insertData() {
139         // Creates an instance of the ContentValues map type expected by database insertions
140         ContentValues values = new ContentValues();
141 
142         // Sets up test data
143         for (int index = 0; index < TEST_NOTES.length; index++) {
144 
145             // Set the creation and modification date for the note
146             TEST_NOTES[index].setCreationDate(START_DATE + (index * ONE_DAY_MILLIS));
147             TEST_NOTES[index].setModificationDate(START_DATE + (index * ONE_WEEK_MILLIS));
148 
149             // Adds a record to the database.
150             mDb.insertOrThrow(
151                 NotePad.Notes.TABLE_NAME,             // the table name for the insert
152                 NotePad.Notes.COLUMN_NAME_TITLE,      // column set to null if empty values map
153                 TEST_NOTES[index].getContentValues()  // the values map to insert
154             );
155         }
156     }
157 
158     /*
159      * Tests the provider's publicly available URIs. If the URI is not one that the provider
160      * understands, the provider should throw an exception. It also tests the provider's getType()
161      * method for each URI, which should return the MIME type associated with the URI.
162      */
testUriAndGetType()163     public void testUriAndGetType() {
164         // Tests the MIME type for the notes table URI.
165         String mimeType = mMockResolver.getType(NotePad.Notes.CONTENT_URI);
166         assertEquals(NotePad.Notes.CONTENT_TYPE, mimeType);
167 
168         // Tests the MIME type for the live folder URI.
169         mimeType = mMockResolver.getType(NotePad.Notes.LIVE_FOLDER_URI);
170         assertEquals(NotePad.Notes.CONTENT_TYPE, mimeType);
171 
172         // Creates a URI with a pattern for note ids. The id doesn't have to exist.
173         Uri noteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, 1);
174 
175         // Gets the note ID URI MIME type.
176         mimeType = mMockResolver.getType(noteIdUri);
177         assertEquals(NotePad.Notes.CONTENT_ITEM_TYPE, mimeType);
178 
179         // Tests an invalid URI. This should throw an IllegalArgumentException.
180         mimeType = mMockResolver.getType(INVALID_URI);
181     }
182 
183     /*
184      * Tests the provider's stream MIME types returned by getStreamTypes(). If the provider supports
185      * stream data for the URI, the MIME type is returned. Otherwise, the provider returns null.
186      */
testGetStreamTypes()187     public void testGetStreamTypes() {
188 
189         // Tests the notes table URI. This should return null, since the content provider does
190         // not provide a stream MIME type for multiple notes.
191         assertNull(mMockResolver.getStreamTypes(NotePad.Notes.CONTENT_URI, MIME_TYPES_ALL));
192 
193         // Tests the live folders URI. This should return null, since the content provider does not
194         // provide a stream MIME type for multiple notes.
195         assertNull(mMockResolver.getStreamTypes(NotePad.Notes.LIVE_FOLDER_URI, MIME_TYPES_ALL));
196 
197         /*
198          * Tests the note id URI for a single note, using _ID value "1" which is a valid ID. Uses a
199          * valid MIME type filter that will return all the supported MIME types for a content URI.
200          * The result should be "text/plain".
201          */
202 
203         // Constructs the note id URI
204         Uri testUri = Uri.withAppendedPath(NotePad.Notes.CONTENT_ID_URI_BASE, "1");
205 
206         // Gets the MIME types for the URI, with the filter that selects all MIME types.
207         String mimeType[] = mMockResolver.getStreamTypes(testUri, MIME_TYPES_ALL);
208 
209         // Tests that the result is not null and is equal to the expected value. Also tests that
210         // only one MIME type is returned.
211         assertNotNull(mimeType);
212         assertEquals(mimeType[0],"text/plain");
213         assertEquals(mimeType.length,1);
214 
215         /*
216          * Tests with the same URI but with a filter that should not return any URIs.
217          */
218         mimeType = mMockResolver.getStreamTypes(testUri, MIME_TYPES_NONE);
219         assertNull(mimeType);
220 
221         /*
222          * Tests with a URI that should not have any associated stream MIME types, but with a
223          * filter that returns all types. The result should still be null.
224          */
225         mimeType = mMockResolver.getStreamTypes(NotePad.Notes.CONTENT_URI, MIME_TYPES_ALL);
226         assertNull(mimeType);
227 
228     }
229 
230     /*
231      * Tests the provider's public API for opening a read-only pipe of data for a note ID URI
232      * and MIME type filter matching "text/plain".
233      * This method throws a FileNotFoundException if the URI isn't for a note ID or the MIME type
234      * filter isn't "text/plain". It throws an IOException if it can't close a file descriptor.
235      */
testOpenTypedAssetFile()236     public void testOpenTypedAssetFile() throws FileNotFoundException, IOException {
237 
238         // A URI to contain a note ID content URI.
239         Uri testNoteIdUri;
240 
241         // A handle for the file descriptor returned by openTypedAssetFile().
242         AssetFileDescriptor testAssetDescriptor;
243 
244         // Inserts data into the provider, so that the note ID URI will be recognized.
245         insertData();
246 
247         // Constructs a URI with a note ID of 1. This matches the note ID URI pattern that
248         // openTypedAssetFile can handle.
249         testNoteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, 1);
250 
251         // Opens the pipe. The opts argument is for passing options from a caller to the provider,
252         // but the NotePadProvider does not use it.
253         testAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor(
254                 testNoteIdUri,         // the URI for a single note. The pipe points to this
255                                        // note's data
256                 MIME_TYPE_TEXT,        // a MIME type of "text/plain"
257                 null                   // the "opts" argument
258         );
259 
260         // Gets the parcel file handle from the asset file handle.
261         ParcelFileDescriptor testParcelDescriptor = testAssetDescriptor.getParcelFileDescriptor();
262 
263         // Gets the file handle from the asset file handle.
264         FileDescriptor testDescriptor = testAssetDescriptor.getFileDescriptor();
265 
266         // Tests that the asset file handle is not null.
267         assertNotNull(testAssetDescriptor);
268 
269         // Tests that the parcel file handle is not null.
270         assertNotNull(testParcelDescriptor);
271 
272         // Tests that the file handle is not null.
273         assertNotNull(testDescriptor);
274 
275         // Tests that the file handle is valid.
276         assertTrue(testDescriptor.valid());
277 
278         // Closes the file handles.
279         testParcelDescriptor.close();
280         testAssetDescriptor.close();
281 
282         /*
283          * Changes the URI to a notes URI for multiple notes, and re-test. This should fail, since
284          * the provider does not support this type of URI. A FileNotFound exception is expected,
285          * so call fail() if it does *not* occur.
286          */
287         try {
288             testAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor(
289                     NotePad.Notes.CONTENT_URI,
290                     MIME_TYPE_TEXT,
291                     null
292             );
293             fail();
294         } catch (FileNotFoundException e) {
295             // continue
296         }
297 
298         /*
299          * Changes back to the note ID URI, but changes the MIME type filter to one that is not
300          * supported by the provider. This should also fail, since the provider will only open a
301          * pipe for MIME type "text/plain". A FileNotFound exception is expected, so calls
302          * fail() if it does *not* occur.
303          */
304 
305         try {
306             testAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor(
307                     testNoteIdUri,
308                     MIME_TYPES_NONE,
309                     null
310             );
311             fail();
312         } catch (FileNotFoundException e) {
313             // continue
314         }
315 
316     }
317 
318     /*
319      * Tests the provider's method for actually returning writing data into a pipe. The method is
320      * writeDataToPipe, but this method is not called directly. Instead, a caller invokes
321      * openTypedAssetFile(). That method uses ContentProvider.openPipeHelper(), which has as one of
322      * its arguments a ContentProvider.PipeDataWriter object that must actually put the data into
323      * the pipe. PipeDataWriter is an interface, not a class, so it must be implemented.
324      *
325      * The NotePadProvider class itself implements the "ContentProvider.PipeDataWriter, which means
326      * that it supplies the interface's only method, writeDataToPipe(). In effect, a call to
327      * openTypedAssetFile() calls writeDataToPipe().
328      *
329      *  The test of writeDataToPipe() is separate from other tests of openTypedAssetFile() for the
330      *  sake of clarity.
331      */
testWriteDataToPipe()332     public void testWriteDataToPipe() throws FileNotFoundException {
333 
334         // A string array to hold the incoming data
335         String[] inputData = {"","",""};
336 
337         // A URI for a note ID.
338         Uri noteIdUri;
339 
340         // A Cursor to contain the retrieved note.
341         Cursor noteIdCursor;
342 
343         // An AssetFileDescriptor for the pipe.
344         AssetFileDescriptor noteIdAssetDescriptor;
345 
346         // The ParcelFileDescriptor in the AssetFileDescriptor
347         ParcelFileDescriptor noteIdParcelDescriptor;
348 
349         // Inserts test data into the provider.
350         insertData();
351 
352         // Creates note ID URI for a note that should now be in the provider.
353         noteIdUri = ContentUris.withAppendedId(
354                 NotePad.Notes.CONTENT_ID_URI_BASE,  // The base pattern for a note ID URI
355                 1                                   // Sets the URI to point to record ID 1 in the
356                                                     // provider
357         );
358 
359         // Gets a Cursor for the note.
360         noteIdCursor = mMockResolver.query(
361                 noteIdUri,  // the URI for the note ID we want to retrieve
362                 null,       // no projection, retrieve all the columns
363                 null,       // no WHERE clause
364                 null,       // no WHERE arguments
365                 null        // default sort order
366         );
367 
368         // Checks that the call worked.
369         // a) Checks that the cursor is not null
370         // b) Checks that it contains a single record
371         assertNotNull(noteIdCursor);
372         assertEquals(1,noteIdCursor.getCount());
373 
374         // Opens the pipe that will contain the data.
375         noteIdAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor(
376                 noteIdUri,        // the URI of the note that will provide the data
377                 MIME_TYPE_TEXT,   // the "text/plain" MIME type
378                 null              // no other options
379         );
380 
381         // Checks that the call worked.
382         // a) checks that the AssetFileDescriptor is not null
383         // b) gets its ParcelFileDescriptor
384         // c) checks that the ParcelFileDescriptor is not null
385         assertNotNull(noteIdAssetDescriptor);
386         noteIdParcelDescriptor = noteIdAssetDescriptor.getParcelFileDescriptor();
387         assertNotNull(noteIdParcelDescriptor);
388 
389         // Gets a File Reader that can read the pipe.
390         FileReader fIn = new FileReader(noteIdParcelDescriptor.getFileDescriptor());
391 
392         // Gets a buffered reader wrapper for the File Reader. This allows reading line by line.
393         BufferedReader bIn = new BufferedReader(fIn);
394 
395         /*
396          * The pipe should contain three lines: The note's title, an empty line, and the note's
397          * contents. The following code reads and stores these three lines.
398          */
399         for (int index = 0; index < inputData.length; index++) {
400             try {
401                 inputData[index] = bIn.readLine();
402             } catch (IOException e) {
403 
404                 e.printStackTrace();
405                 fail();
406             }
407         }
408 
409         // Asserts that the first record in the provider (written from TEST_NOTES[0]) has the same
410         // note title as the first line retrieved from the pipe.
411         assertEquals(TEST_NOTES[0].title, inputData[0]);
412 
413         // Asserts that the first record in the provider (written from TEST_NOTES[0]) has the same
414         // note contents as the third line retrieved from the pipe.
415         assertEquals(TEST_NOTES[0].note, inputData[2]);
416     }
417 
418     /*
419      * Tests the provider's public API for querying data in the table, using the URI for
420      * a dataset of records.
421      */
testQueriesOnNotesUri()422     public void testQueriesOnNotesUri() {
423         // Defines a projection of column names to return for a query
424         final String[] TEST_PROJECTION = {
425             NotePad.Notes.COLUMN_NAME_TITLE,
426             NotePad.Notes.COLUMN_NAME_NOTE,
427             NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE
428         };
429 
430         // Defines a selection column for the query. When the selection columns are passed
431         // to the query, the selection arguments replace the placeholders.
432         final String TITLE_SELECTION = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?";
433 
434         // Defines the selection columns for a query.
435         final String SELECTION_COLUMNS =
436             TITLE_SELECTION + " OR " + TITLE_SELECTION + " OR " + TITLE_SELECTION;
437 
438          // Defines the arguments for the selection columns.
439         final String[] SELECTION_ARGS = { "Note0", "Note1", "Note5" };
440 
441          // Defines a query sort order
442         final String SORT_ORDER = NotePad.Notes.COLUMN_NAME_TITLE + " ASC";
443 
444         // Query subtest 1.
445         // If there are no records in the table, the returned cursor from a query should be empty.
446         Cursor cursor = mMockResolver.query(
447             NotePad.Notes.CONTENT_URI,  // the URI for the main data table
448             null,                       // no projection, get all columns
449             null,                       // no selection criteria, get all records
450             null,                       // no selection arguments
451             null                        // use default sort order
452         );
453 
454          // Asserts that the returned cursor contains no records
455         assertEquals(0, cursor.getCount());
456 
457          // Query subtest 2.
458          // If the table contains records, the returned cursor from a query should contain records.
459 
460         // Inserts the test data into the provider's underlying data source
461         insertData();
462 
463         // Gets all the columns for all the rows in the table
464         cursor = mMockResolver.query(
465             NotePad.Notes.CONTENT_URI,  // the URI for the main data table
466             null,                       // no projection, get all columns
467             null,                       // no selection criteria, get all records
468             null,                       // no selection arguments
469             null                        // use default sort order
470         );
471 
472         // Asserts that the returned cursor contains the same number of rows as the size of the
473         // test data array.
474         assertEquals(TEST_NOTES.length, cursor.getCount());
475 
476         // Query subtest 3.
477         // A query that uses a projection should return a cursor with the same number of columns
478         // as the projection, with the same names, in the same order.
479         Cursor projectionCursor = mMockResolver.query(
480               NotePad.Notes.CONTENT_URI,  // the URI for the main data table
481               TEST_PROJECTION,            // get the title, note, and mod date columns
482               null,                       // no selection columns, get all the records
483               null,                       // no selection criteria
484               null                        // use default the sort order
485         );
486 
487         // Asserts that the number of columns in the cursor is the same as in the projection
488         assertEquals(TEST_PROJECTION.length, projectionCursor.getColumnCount());
489 
490         // Asserts that the names of the columns in the cursor and in the projection are the same.
491         // This also verifies that the names are in the same order.
492         assertEquals(TEST_PROJECTION[0], projectionCursor.getColumnName(0));
493         assertEquals(TEST_PROJECTION[1], projectionCursor.getColumnName(1));
494         assertEquals(TEST_PROJECTION[2], projectionCursor.getColumnName(2));
495 
496         // Query subtest 4
497         // A query that uses selection criteria should return only those rows that match the
498         // criteria. Use a projection so that it's easy to get the data in a particular column.
499         projectionCursor = mMockResolver.query(
500             NotePad.Notes.CONTENT_URI, // the URI for the main data table
501             TEST_PROJECTION,           // get the title, note, and mod date columns
502             SELECTION_COLUMNS,         // select on the title column
503             SELECTION_ARGS,            // select titles "Note0", "Note1", or "Note5"
504             SORT_ORDER                 // sort ascending on the title column
505         );
506 
507         // Asserts that the cursor has the same number of rows as the number of selection arguments
508         assertEquals(SELECTION_ARGS.length, projectionCursor.getCount());
509 
510         int index = 0;
511 
512         while (projectionCursor.moveToNext()) {
513 
514             // Asserts that the selection argument at the current index matches the value of
515             // the title column (column 0) in the current record of the cursor
516             assertEquals(SELECTION_ARGS[index], projectionCursor.getString(0));
517 
518             index++;
519         }
520 
521         // Asserts that the index pointer is now the same as the number of selection arguments, so
522         // that the number of arguments tested is exactly the same as the number of rows returned.
523         assertEquals(SELECTION_ARGS.length, index);
524 
525     }
526 
527     /*
528      * Tests queries against the provider, using the note id URI. This URI encodes a single
529      * record ID. The provider should only return 0 or 1 record.
530      */
testQueriesOnNoteIdUri()531     public void testQueriesOnNoteIdUri() {
532       // Defines the selection column for a query. The "?" is replaced by entries in the
533       // selection argument array
534       final String SELECTION_COLUMNS = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?";
535 
536       // Defines the argument for the selection column.
537       final String[] SELECTION_ARGS = { "Note1" };
538 
539       // A sort order for the query.
540       final String SORT_ORDER = NotePad.Notes.COLUMN_NAME_TITLE + " ASC";
541 
542       // Creates a projection includes the note id column, so that note id can be retrieved.
543       final String[] NOTE_ID_PROJECTION = {
544            NotePad.Notes._ID,                 // The Notes class extends BaseColumns,
545                                               // which includes _ID as the column name for the
546                                               // record's id in the data model
547            NotePad.Notes.COLUMN_NAME_TITLE};  // The note's title
548 
549       // Query subtest 1.
550       // Tests that a query against an empty table returns null.
551 
552       // Constructs a URI that matches the provider's notes id URI pattern, using an arbitrary
553       // value of 1 as the note ID.
554       Uri noteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, 1);
555 
556       // Queries the table with the notes ID URI. This should return an empty cursor.
557       Cursor cursor = mMockResolver.query(
558           noteIdUri, // URI pointing to a single record
559           null,      // no projection, get all the columns for each record
560           null,      // no selection criteria, get all the records in the table
561           null,      // no need for selection arguments
562           null       // default sort, by ascending title
563       );
564 
565       // Asserts that the cursor is null.
566       assertEquals(0,cursor.getCount());
567 
568       // Query subtest 2.
569       // Tests that a query against a table containing records returns a single record whose ID
570       // is the one requested in the URI provided.
571 
572       // Inserts the test data into the provider's underlying data source.
573       insertData();
574 
575       // Queries the table using the URI for the full table.
576       cursor = mMockResolver.query(
577           NotePad.Notes.CONTENT_URI, // the base URI for the table
578           NOTE_ID_PROJECTION,        // returns the ID and title columns of rows
579           SELECTION_COLUMNS,         // select based on the title column
580           SELECTION_ARGS,            // select title of "Note1"
581           SORT_ORDER                 // sort order returned is by title, ascending
582       );
583 
584       // Asserts that the cursor contains only one row.
585       assertEquals(1, cursor.getCount());
586 
587       // Moves to the cursor's first row, and asserts that this did not fail.
588       assertTrue(cursor.moveToFirst());
589 
590       // Saves the record's note ID.
591       int inputNoteId = cursor.getInt(0);
592 
593       // Builds a URI based on the provider's content ID URI base and the saved note ID.
594       noteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, inputNoteId);
595 
596       // Queries the table using the content ID URI, which returns a single record with the
597       // specified note ID, matching the selection criteria provided.
598       cursor = mMockResolver.query(noteIdUri, // the URI for a single note
599           NOTE_ID_PROJECTION,                 // same projection, get ID and title columns
600           SELECTION_COLUMNS,                  // same selection, based on title column
601           SELECTION_ARGS,                     // same selection arguments, title = "Note1"
602           SORT_ORDER                          // same sort order returned, by title, ascending
603       );
604 
605       // Asserts that the cursor contains only one row.
606       assertEquals(1, cursor.getCount());
607 
608       // Moves to the cursor's first row, and asserts that this did not fail.
609       assertTrue(cursor.moveToFirst());
610 
611       // Asserts that the note ID passed to the provider is the same as the note ID returned.
612       assertEquals(inputNoteId, cursor.getInt(0));
613     }
614 
615     /*
616      *  Tests inserts into the data model.
617      */
testInserts()618     public void testInserts() {
619         // Creates a new note instance with ID of 30.
620         NoteInfo note = new NoteInfo(
621             "Note30", // the note's title
622             "Test inserting a note" // the note's content
623         );
624 
625         // Sets the note's creation and modification times
626         note.setCreationDate(START_DATE + (10 * ONE_DAY_MILLIS));
627         note.setModificationDate(START_DATE + (2 * ONE_WEEK_MILLIS));
628 
629         // Insert subtest 1.
630         // Inserts a row using the new note instance.
631         // No assertion will be done. The insert() method either works or throws an Exception
632         Uri rowUri = mMockResolver.insert(
633             NotePad.Notes.CONTENT_URI,  // the main table URI
634             note.getContentValues()     // the map of values to insert as a new record
635         );
636 
637         // Parses the returned URI to get the note ID of the new note. The ID is used in subtest 2.
638         long noteId = ContentUris.parseId(rowUri);
639 
640         // Does a full query on the table. Since insertData() hasn't yet been called, the
641         // table should only contain the record just inserted.
642         Cursor cursor = mMockResolver.query(
643             NotePad.Notes.CONTENT_URI, // the main table URI
644             null,                      // no projection, return all the columns
645             null,                      // no selection criteria, return all the rows in the model
646             null,                      // no selection arguments
647             null                       // default sort order
648         );
649 
650         // Asserts that there should be only 1 record.
651         assertEquals(1, cursor.getCount());
652 
653         // Moves to the first (and only) record in the cursor and asserts that this worked.
654         assertTrue(cursor.moveToFirst());
655 
656         // Since no projection was used, get the column indexes of the returned columns
657         int titleIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE);
658         int noteIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE);
659         int crdateIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_CREATE_DATE);
660         int moddateIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE);
661 
662         // Tests each column in the returned cursor against the data that was inserted, comparing
663         // the field in the NoteInfo object to the data at the column index in the cursor.
664         assertEquals(note.title, cursor.getString(titleIndex));
665         assertEquals(note.note, cursor.getString(noteIndex));
666         assertEquals(note.createDate, cursor.getLong(crdateIndex));
667         assertEquals(note.modDate, cursor.getLong(moddateIndex));
668 
669         // Insert subtest 2.
670         // Tests that we can't insert a record whose id value already exists.
671 
672         // Defines a ContentValues object so that the test can add a note ID to it.
673         ContentValues values = note.getContentValues();
674 
675         // Adds the note ID retrieved in subtest 1 to the ContentValues object.
676         values.put(NotePad.Notes._ID, (int) noteId);
677 
678         // Tries to insert this record into the table. This should fail and drop into the
679         // catch block. If it succeeds, issue a failure message.
680         try {
681             rowUri = mMockResolver.insert(NotePad.Notes.CONTENT_URI, values);
682             fail("Expected insert failure for existing record but insert succeeded.");
683         } catch (Exception e) {
684           // succeeded, so do nothing.
685         }
686     }
687 
688     /*
689      * Tests deletions from the data model.
690      */
testDeletes()691     public void testDeletes() {
692         // Subtest 1.
693         // Tries to delete a record from a data model that is empty.
694 
695         // Sets the selection column to "title"
696         final String SELECTION_COLUMNS = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?";
697 
698         // Sets the selection argument "Note0"
699         final String[] SELECTION_ARGS = { "Note0" };
700 
701         // Tries to delete rows matching the selection criteria from the data model.
702         int rowsDeleted = mMockResolver.delete(
703             NotePad.Notes.CONTENT_URI, // the base URI of the table
704             SELECTION_COLUMNS,         // select based on the title column
705             SELECTION_ARGS             // select title = "Note0"
706         );
707 
708         // Assert that the deletion did not work. The number of deleted rows should be zero.
709         assertEquals(0, rowsDeleted);
710 
711         // Subtest 2.
712         // Tries to delete an existing record. Repeats the previous subtest, but inserts data first.
713 
714         // Inserts data into the model.
715         insertData();
716 
717         // Uses the same parameters to try to delete the row with title "Note0"
718         rowsDeleted = mMockResolver.delete(
719             NotePad.Notes.CONTENT_URI, // the base URI of the table
720             SELECTION_COLUMNS,         // same selection column, "title"
721             SELECTION_ARGS             // same selection arguments, title = "Note0"
722         );
723 
724         // The number of deleted rows should be 1.
725         assertEquals(1, rowsDeleted);
726 
727         // Tests that the record no longer exists. Tries to get it from the table, and
728         // asserts that nothing was returned.
729 
730         // Queries the table with the same selection column and argument used to delete the row.
731         Cursor cursor = mMockResolver.query(
732             NotePad.Notes.CONTENT_URI, // the base URI of the table
733             null,                      // no projection, return all columns
734             SELECTION_COLUMNS,         // select based on the title column
735             SELECTION_ARGS,            // select title = "Note0"
736             null                       // use the default sort order
737         );
738 
739         // Asserts that the cursor is empty since the record had already been deleted.
740         assertEquals(0, cursor.getCount());
741     }
742 
743     /*
744      * Tests updates to the data model.
745      */
testUpdates()746     public void testUpdates() {
747         // Selection column for identifying a record in the data model.
748         final String SELECTION_COLUMNS = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?";
749 
750         // Selection argument for the selection column.
751         final String[] selectionArgs = { "Note1" };
752 
753         // Defines a map of column names and values
754         ContentValues values = new ContentValues();
755 
756         // Subtest 1.
757         // Tries to update a record in an empty table.
758 
759         // Sets up the update by putting the "note" column and a value into the values map.
760         values.put(NotePad.Notes.COLUMN_NAME_NOTE, "Testing an update with this string");
761 
762         // Tries to update the table
763         int rowsUpdated = mMockResolver.update(
764             NotePad.Notes.CONTENT_URI,  // the URI of the data table
765             values,                     // a map of the updates to do (column title and value)
766             SELECTION_COLUMNS,           // select based on the title column
767             selectionArgs               // select "title = Note1"
768         );
769 
770         // Asserts that no rows were updated.
771         assertEquals(0, rowsUpdated);
772 
773         // Subtest 2.
774         // Builds the table, and then tries the update again using the same arguments.
775 
776         // Inserts data into the model.
777         insertData();
778 
779         //  Does the update again, using the same arguments as in subtest 1.
780         rowsUpdated = mMockResolver.update(
781             NotePad.Notes.CONTENT_URI,   // The URI of the data table
782             values,                      // the same map of updates
783             SELECTION_COLUMNS,            // same selection, based on the title column
784             selectionArgs                // same selection argument, to select "title = Note1"
785         );
786 
787         // Asserts that only one row was updated. The selection criteria evaluated to
788         // "title = Note1", and the test data should only contain one row that matches that.
789         assertEquals(1, rowsUpdated);
790 
791     }
792 
793     // A utility for converting note data to a ContentValues map.
794     private static class NoteInfo {
795         String title;
796         String note;
797         long createDate;
798         long modDate;
799 
800         /*
801          * Constructor for a NoteInfo instance. This class helps create a note and
802          * return its values in a ContentValues map expected by data model methods.
803          * The note's id is created automatically when it is inserted into the data model.
804          */
NoteInfo(String t, String n)805         public NoteInfo(String t, String n) {
806             title = t;
807             note = n;
808             createDate = 0;
809             modDate = 0;
810         }
811 
812         // Sets the creation date for a test note
setCreationDate(long c)813         public void setCreationDate(long c) {
814             createDate = c;
815         }
816 
817         // Sets the modification date for a test note
setModificationDate(long m)818         public void setModificationDate(long m) {
819             modDate = m;
820         }
821 
822         /*
823          * Returns a ContentValues instance (a map) for this NoteInfo instance. This is useful for
824          * inserting a NoteInfo into a database.
825          */
getContentValues()826         public ContentValues getContentValues() {
827             // Gets a new ContentValues object
828             ContentValues v = new ContentValues();
829 
830             // Adds map entries for the user-controlled fields in the map
831             v.put(NotePad.Notes.COLUMN_NAME_TITLE, title);
832             v.put(NotePad.Notes.COLUMN_NAME_NOTE, note);
833             v.put(NotePad.Notes.COLUMN_NAME_CREATE_DATE, createDate);
834             v.put(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, modDate);
835             return v;
836 
837         }
838     }
839 }
840