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
17package com.example.android.notepad;
18
19import android.content.ContentUris;
20import android.content.ContentValues;
21import android.content.res.AssetFileDescriptor;
22import android.database.Cursor;
23import android.database.sqlite.SQLiteDatabase;
24import android.net.Uri;
25import android.os.ParcelFileDescriptor;
26import android.test.ProviderTestCase2;
27import android.test.mock.MockContentResolver;
28
29import java.io.BufferedReader;
30import java.io.FileDescriptor;
31import java.io.FileNotFoundException;
32import java.io.FileReader;
33import java.io.IOException;
34import java.util.Calendar;
35import 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 */
45public 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     */
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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         */
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
813        public void setCreationDate(long c) {
814            createDate = c;
815        }
816
817        // Sets the modification date for a test note
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         */
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