Skip to content

Commit ba9a4e8

Browse files
miltonialsjamescr
authored andcommitted
test: add confirming bug tests for database helper
1 parent 32e15e0 commit ba9a4e8

1 file changed

Lines changed: 211 additions & 0 deletions

File tree

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package net.osmtracker.db;
2+
3+
import android.content.ContentValues;
4+
import android.content.Context;
5+
import android.database.Cursor;
6+
import android.database.sqlite.SQLiteDatabase;
7+
8+
import androidx.test.core.app.ApplicationProvider;
9+
10+
import org.junit.After;
11+
import org.junit.Before;
12+
import org.junit.Test;
13+
import org.junit.runner.RunWith;
14+
import org.robolectric.RobolectricTestRunner;
15+
import org.robolectric.annotation.Config;
16+
17+
import static org.junit.Assert.assertEquals;
18+
import static org.junit.Assert.assertFalse;
19+
import static org.junit.Assert.assertTrue;
20+
21+
/**
22+
* Bug-confirming tests for {@link DatabaseHelper}.
23+
*
24+
* <p>Every test in this class documents a <strong>known bug</strong> by asserting the current
25+
* broken behaviour. Each test passes <em>because</em> the bug exists — the tests are expected
26+
* to <strong>fail</strong> once the corresponding bug is fixed.
27+
*
28+
* <p>When a bug is fixed:
29+
* <ol>
30+
* <li>Remove the {@literal @}Ignore annotation from the matching test in
31+
* {@link DatabaseHelperTest} (the intended-behaviour companion).</li>
32+
* <li>Delete (or permanently skip) the test in this class — it no longer represents
33+
* correct expected behaviour.</li>
34+
* <li>Run {@code ./gradlew testDebugUnitTest} — the formerly-{@literal @}Ignored test in
35+
* {@link DatabaseHelperTest} must now pass.</li>
36+
* </ol>
37+
*
38+
* <p>See {@code docs/BUGS_DatabaseHelper.md} for the full description of each bug.
39+
*/
40+
@RunWith(RobolectricTestRunner.class)
41+
@Config(sdk = 25)
42+
public class DatabaseHelperTestBugs {
43+
44+
private DatabaseHelper dbHelper;
45+
46+
@Before
47+
public void setUp() {
48+
Context context = ApplicationProvider.getApplicationContext();
49+
dbHelper = new DatabaseHelper(context);
50+
}
51+
52+
@After
53+
public void tearDown() {
54+
dbHelper.close();
55+
}
56+
57+
// ── Shared helpers ────────────────────────────────────────────────────────
58+
59+
/**
60+
* Returns true if the given column has notnull=1 in PRAGMA table_info.
61+
*/
62+
private boolean isColumnNotNull(SQLiteDatabase database, String table, String column) {
63+
Cursor c = database.rawQuery("PRAGMA table_info(" + table + ")", null);
64+
try {
65+
int nameIdx = c.getColumnIndex("name");
66+
int notNullIdx = c.getColumnIndex("notnull");
67+
while (c.moveToNext()) {
68+
if (column.equals(c.getString(nameIdx))) {
69+
return c.getInt(notNullIdx) == 1;
70+
}
71+
}
72+
} finally {
73+
c.close();
74+
}
75+
return false;
76+
}
77+
78+
// ── Bug B10: segment_id missing NOT NULL after upgrade from v18 ──────────
79+
80+
/**
81+
* Bug B10 — After upgrading from v18 to v19, {@code segment_id} is nullable.
82+
*
83+
* <p>The {@code ALTER TABLE} in {@code onUpgrade()} case 18 uses
84+
* {@code "integer default 0"} without {@code "not null"}, so after an upgrade
85+
* the column permits NULL values. In contrast, a fresh install via
86+
* {@code onCreate()} defines the column as {@code "integer not null default 0"}.
87+
*
88+
* <p>This test passes because the bug exists (notnull is 0 after upgrade).
89+
* When Bug B10 is fixed, this test will fail and should be deleted.
90+
* Remove {@literal @}Ignore from
91+
* {@code DatabaseHelperTest#onUpgrade_from18to19_segmentId_shouldBeNotNull}.
92+
*
93+
* @see DatabaseHelperTest#onUpgrade_from18to19_segmentId_shouldBeNotNull
94+
*/
95+
@Test
96+
public void bug_B10_segmentId_missingNotNull_afterUpgradeFrom18() {
97+
SQLiteDatabase rawDb = SQLiteDatabase.create(null);
98+
try {
99+
// Simulate v18 trackpoint schema (no segment_id)
100+
rawDb.execSQL("create table trackpoint ("
101+
+ "_id integer primary key autoincrement,"
102+
+ "track_id integer not null,"
103+
+ "latitude double not null,"
104+
+ "longitude double not null,"
105+
+ "speed double null,"
106+
+ "elevation double null,"
107+
+ "accuracy double null,"
108+
+ "point_timestamp long not null,"
109+
+ "compass_heading double null,"
110+
+ "compass_accuracy integer null,"
111+
+ "atmospheric_pressure double null)");
112+
113+
dbHelper.onUpgrade(rawDb, 18, 19);
114+
115+
assertFalse("Bug B10: segment_id should be NOT NULL but is nullable after upgrade",
116+
isColumnNotNull(rawDb, TrackContentProvider.Schema.TBL_TRACKPOINT,
117+
TrackContentProvider.Schema.COL_SEG_ID));
118+
} finally {
119+
rawDb.close();
120+
}
121+
}
122+
123+
/**
124+
* Bug B10 — NULL can be inserted into segment_id after an upgrade from v18.
125+
*
126+
* <p>Because the ALTER TABLE omits NOT NULL, a NULL value can be successfully
127+
* inserted into segment_id on an upgraded database. This would never happen
128+
* on a fresh install where NOT NULL is enforced.
129+
*/
130+
@Test
131+
public void bug_B10_segmentId_allowsNullInsert_afterUpgrade() {
132+
SQLiteDatabase rawDb = SQLiteDatabase.create(null);
133+
try {
134+
rawDb.execSQL("create table trackpoint ("
135+
+ "_id integer primary key autoincrement,"
136+
+ "track_id integer not null,"
137+
+ "latitude double not null,"
138+
+ "longitude double not null,"
139+
+ "speed double null,"
140+
+ "elevation double null,"
141+
+ "accuracy double null,"
142+
+ "point_timestamp long not null,"
143+
+ "compass_heading double null,"
144+
+ "compass_accuracy integer null,"
145+
+ "atmospheric_pressure double null)");
146+
147+
dbHelper.onUpgrade(rawDb, 18, 19);
148+
149+
// Insert with explicit NULL for segment_id
150+
ContentValues values = new ContentValues();
151+
values.put(TrackContentProvider.Schema.COL_TRACK_ID, 1);
152+
values.put(TrackContentProvider.Schema.COL_LATITUDE, 48.0);
153+
values.put(TrackContentProvider.Schema.COL_LONGITUDE, 2.0);
154+
values.put(TrackContentProvider.Schema.COL_TIMESTAMP, System.currentTimeMillis());
155+
values.putNull(TrackContentProvider.Schema.COL_SEG_ID);
156+
157+
long rowId = rawDb.insert(TrackContentProvider.Schema.TBL_TRACKPOINT, null, values);
158+
assertTrue("Bug B10: NULL insert into segment_id should succeed on upgraded DB",
159+
rowId > 0);
160+
161+
// Verify the value is actually NULL
162+
Cursor c = rawDb.query(TrackContentProvider.Schema.TBL_TRACKPOINT,
163+
new String[]{TrackContentProvider.Schema.COL_SEG_ID},
164+
"_id = ?", new String[]{String.valueOf(rowId)},
165+
null, null, null);
166+
try {
167+
assertTrue(c.moveToFirst());
168+
assertTrue("Bug B10: segment_id should be NULL", c.isNull(0));
169+
} finally {
170+
c.close();
171+
}
172+
} finally {
173+
rawDb.close();
174+
}
175+
}
176+
177+
/**
178+
* Bug B10 baseline — on a fresh install, segment_id enforces NOT NULL
179+
* (or at least defaults to 0 when NULL is attempted).
180+
*
181+
* <p>This establishes the correct baseline against which the upgrade path diverges.
182+
* On fresh installs, SQLite's NOT NULL with DEFAULT 0 causes a NULL insert to
183+
* use the default value of 0 instead.
184+
*/
185+
@Test
186+
public void bug_B10_segmentId_freshInstall_defaultsToZeroWhenNullInserted() {
187+
SQLiteDatabase db = dbHelper.getWritableDatabase();
188+
189+
ContentValues values = new ContentValues();
190+
values.put(TrackContentProvider.Schema.COL_TRACK_ID, 1);
191+
values.put(TrackContentProvider.Schema.COL_LATITUDE, 48.0);
192+
values.put(TrackContentProvider.Schema.COL_LONGITUDE, 2.0);
193+
values.put(TrackContentProvider.Schema.COL_TIMESTAMP, System.currentTimeMillis());
194+
// Don't set segment_id — should default to 0
195+
196+
long rowId = db.insert(TrackContentProvider.Schema.TBL_TRACKPOINT, null, values);
197+
assertTrue("insert should succeed on fresh DB", rowId > 0);
198+
199+
Cursor c = db.query(TrackContentProvider.Schema.TBL_TRACKPOINT,
200+
new String[]{TrackContentProvider.Schema.COL_SEG_ID},
201+
"_id = ?", new String[]{String.valueOf(rowId)},
202+
null, null, null);
203+
try {
204+
assertTrue(c.moveToFirst());
205+
assertEquals("segment_id should default to 0 on fresh install",
206+
0, c.getInt(0));
207+
} finally {
208+
c.close();
209+
}
210+
}
211+
}

0 commit comments

Comments
 (0)