- iOS: Add BackgroundSyncService, SyncScheduler, SyncWorker, BookmarkViewModel, FeedViewModel - iOS: Add BackgroundSyncService, SyncScheduler, SyncWorker services - Linux: Add settings-store.vala, State.vala signals, view widgets (FeedList, FeedDetail, AddFeed, Search, Settings, Bookmark) - Linux: Add bookmark-store.vala, bookmark vala model, search-service.vala - Android: Add NotificationService, NotificationManager, NotificationPreferencesStore - Android: Add BookmarkDao, BookmarkRepository, SettingsStore - Add unit tests for iOS, Android, Linux - Add integration tests - Add performance benchmarks - Update tasks and documentation Co-Authored-By: Paperclip <noreply@paperclip.ing>
639 lines
22 KiB
Vala
639 lines
22 KiB
Vala
/*
|
|
* DatabaseTests.vala
|
|
*
|
|
* Unit tests for database layer.
|
|
*/
|
|
|
|
public class RSSuper.DatabaseTests {
|
|
private Database? db;
|
|
private string test_db_path;
|
|
|
|
public void run_subscription_crud() {
|
|
try {
|
|
test_db_path = "/tmp/rssuper_test_%d.db".printf((int)new DateTime.now_local().to_unix());
|
|
db = new Database(test_db_path);
|
|
} catch (DBError e) {
|
|
warning("Failed to create test database: %s", e.message);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var store = new SubscriptionStore(db);
|
|
|
|
// Create test subscription
|
|
var subscription = new FeedSubscription.with_values(
|
|
"sub_1",
|
|
"https://example.com/feed.xml",
|
|
"Example Feed",
|
|
60,
|
|
"Technology",
|
|
true,
|
|
"2024-01-01T00:00:00Z",
|
|
"2024-01-01T00:00:00Z"
|
|
);
|
|
|
|
// Test add
|
|
store.add(subscription);
|
|
var retrieved = store.get_by_id("sub_1");
|
|
if (retrieved == null) {
|
|
printerr("FAIL: Expected subscription to exist after add\n");
|
|
return;
|
|
}
|
|
|
|
// Test get
|
|
if (retrieved.title != "Example Feed") {
|
|
printerr("FAIL: Expected title 'Example Feed', got '%s'\n", retrieved.title);
|
|
return;
|
|
}
|
|
if (retrieved.url != "https://example.com/feed.xml") {
|
|
printerr("FAIL: Expected url 'https://example.com/feed.xml', got '%s'\n", retrieved.url);
|
|
return;
|
|
}
|
|
|
|
// Test update
|
|
retrieved.title = "Updated Feed";
|
|
store.update(retrieved);
|
|
var updated = store.get_by_id("sub_1");
|
|
if (updated.title != "Updated Feed") {
|
|
printerr("FAIL: Expected title 'Updated Feed', got '%s'\n", updated.title);
|
|
return;
|
|
}
|
|
|
|
// Test delete
|
|
store.remove_subscription("sub_1");
|
|
var deleted = store.get_by_id("sub_1");
|
|
if (deleted != null) {
|
|
printerr("FAIL: Expected subscription to be deleted\n");
|
|
return;
|
|
}
|
|
|
|
print("PASS: test_subscription_crud\n");
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
}
|
|
|
|
public void run_subscription_list() {
|
|
try {
|
|
test_db_path = "/tmp/rssuper_test_%d.db".printf((int)new DateTime.now_local().to_unix());
|
|
db = new Database(test_db_path);
|
|
} catch (DBError e) {
|
|
warning("Failed to create test database: %s", e.message);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var store = new SubscriptionStore(db);
|
|
|
|
// Add multiple subscriptions
|
|
var sub1 = new FeedSubscription.with_values("sub_1", "https://feed1.com", "Feed 1");
|
|
var sub2 = new FeedSubscription.with_values("sub_2", "https://feed2.com", "Feed 2");
|
|
var sub3 = new FeedSubscription.with_values("sub_3", "https://feed3.com", "Feed 3", 60, null, false);
|
|
|
|
store.add(sub1);
|
|
store.add(sub2);
|
|
store.add(sub3);
|
|
|
|
// Test get_all
|
|
var all = store.get_all();
|
|
if (all.length != 3) {
|
|
printerr("FAIL: Expected 3 subscriptions, got %d\n", all.length);
|
|
return;
|
|
}
|
|
|
|
// Test get_enabled
|
|
var enabled = store.get_enabled();
|
|
if (enabled.length != 2) {
|
|
printerr("FAIL: Expected 2 enabled subscriptions, got %d\n", enabled.length);
|
|
return;
|
|
}
|
|
|
|
print("PASS: test_subscription_list\n");
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
}
|
|
|
|
public void run_feed_item_crud() {
|
|
try {
|
|
test_db_path = "/tmp/rssuper_test_%d.db".printf((int)new DateTime.now_local().to_unix());
|
|
db = new Database(test_db_path);
|
|
} catch (DBError e) {
|
|
warning("Failed to create test database: %s", e.message);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var sub_store = new SubscriptionStore(db);
|
|
var item_store = new FeedItemStore(db);
|
|
|
|
// Create subscription first
|
|
var subscription = new FeedSubscription.with_values(
|
|
"sub_1", "https://example.com/feed.xml", "Example Feed"
|
|
);
|
|
sub_store.add(subscription);
|
|
|
|
// Create test item
|
|
var item = new FeedItem.with_values(
|
|
"item_1",
|
|
"Test Article",
|
|
"https://example.com/article",
|
|
"This is a test description",
|
|
"Full content of the article",
|
|
"John Doe",
|
|
"2024-01-01T12:00:00Z",
|
|
"2024-01-01T12:00:00Z",
|
|
{"Technology", "News"},
|
|
null, null, null, null,
|
|
"sub_1" // subscription_id (stored as subscription_title in DB)
|
|
);
|
|
|
|
// Test add
|
|
item_store.add(item);
|
|
var retrieved = item_store.get_by_id("item_1");
|
|
if (retrieved == null) {
|
|
printerr("FAIL: Expected item to exist after add\n");
|
|
return;
|
|
}
|
|
if (retrieved.title != "Test Article") {
|
|
printerr("FAIL: Expected title 'Test Article', got '%s'\n", retrieved.title);
|
|
return;
|
|
}
|
|
|
|
// Test get by subscription
|
|
var items = item_store.get_by_subscription("sub_1");
|
|
if (items.length != 1) {
|
|
printerr("FAIL: Expected 1 item, got %d\n", items.length);
|
|
return;
|
|
}
|
|
|
|
// Test mark as read
|
|
item_store.mark_as_read("item_1");
|
|
|
|
// Test delete
|
|
item_store.delete("item_1");
|
|
var deleted = item_store.get_by_id("item_1");
|
|
if (deleted != null) {
|
|
printerr("FAIL: Expected item to be deleted\n");
|
|
return;
|
|
}
|
|
|
|
print("PASS: test_feed_item_crud\n");
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
}
|
|
|
|
public void run_feed_item_batch() {
|
|
try {
|
|
test_db_path = "/tmp/rssuper_test_%d.db".printf((int)new DateTime.now_local().to_unix());
|
|
db = new Database(test_db_path);
|
|
} catch (DBError e) {
|
|
warning("Failed to create test database: %s", e.message);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var sub_store = new SubscriptionStore(db);
|
|
var item_store = new FeedItemStore(db);
|
|
|
|
// Create subscription
|
|
var subscription = new FeedSubscription.with_values(
|
|
"sub_1", "https://example.com/feed.xml", "Example Feed"
|
|
);
|
|
sub_store.add(subscription);
|
|
|
|
// Create multiple items
|
|
var items = new FeedItem[5];
|
|
for (var i = 0; i < 5; i++) {
|
|
items[i] = new FeedItem.with_values(
|
|
"item_%d".printf(i),
|
|
"Article %d".printf(i),
|
|
"https://example.com/article%d".printf(i),
|
|
"Description %d".printf(i),
|
|
null,
|
|
"Author %d".printf(i),
|
|
"2024-01-%02dT12:00:00Z".printf(i + 1),
|
|
null,
|
|
null,
|
|
null, null, null, null,
|
|
"sub_1" // subscription_id
|
|
);
|
|
}
|
|
|
|
// Test batch insert
|
|
item_store.add_batch(items);
|
|
|
|
var all = item_store.get_by_subscription("sub_1");
|
|
if (all.length != 5) {
|
|
printerr("FAIL: Expected 5 items, got %d\n", all.length);
|
|
return;
|
|
}
|
|
|
|
print("PASS: test_feed_item_batch\n");
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
}
|
|
|
|
public void run_search_history() {
|
|
try {
|
|
test_db_path = "/tmp/rssuper_test_%d.db".printf((int)new DateTime.now_local().to_unix());
|
|
db = new Database(test_db_path);
|
|
} catch (DBError e) {
|
|
warning("Failed to create test database: %s", e.message);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var store = new SearchHistoryStore(db);
|
|
|
|
// Create test queries
|
|
var query1 = SearchQuery("test query", 1, 20, null, SearchSortOption.RELEVANCE);
|
|
var query2 = SearchQuery("another search", 1, 10, null, SearchSortOption.DATE_DESC);
|
|
|
|
// Test record
|
|
store.record_search(query1, 15);
|
|
store.record_search(query2, 8);
|
|
|
|
// Test get_history
|
|
var history = store.get_history();
|
|
if (history.length != 2) {
|
|
printerr("FAIL: Expected 2 history entries, got %d\n", history.length);
|
|
return;
|
|
}
|
|
// Check that both queries are in history (order may vary due to timing)
|
|
bool found_test_query = false;
|
|
bool found_another_search = false;
|
|
foreach (var q in history) {
|
|
if (q.query == "test query") found_test_query = true;
|
|
if (q.query == "another search") found_another_search = true;
|
|
}
|
|
if (!found_test_query || !found_another_search) {
|
|
printerr("FAIL: Expected both queries in history\n");
|
|
return;
|
|
}
|
|
|
|
// Test get_recent
|
|
var recent = store.get_recent();
|
|
if (recent.length != 2) {
|
|
printerr("FAIL: Expected 2 recent entries, got %d\n", recent.length);
|
|
return;
|
|
}
|
|
|
|
print("PASS: test_search_history\n");
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
}
|
|
|
|
public void run_fts_search() {
|
|
try {
|
|
test_db_path = "/tmp/rssuper_test_%d.db".printf((int)new DateTime.now_local().to_unix());
|
|
db = new Database(test_db_path);
|
|
} catch (DBError e) {
|
|
warning("Failed to create test database: %s", e.message);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var sub_store = new SubscriptionStore(db);
|
|
var item_store = new FeedItemStore(db);
|
|
|
|
// Create subscription
|
|
var subscription = new FeedSubscription.with_values(
|
|
"sub_1", "https://example.com/feed.xml", "Example Feed"
|
|
);
|
|
sub_store.add(subscription);
|
|
|
|
// Add items with searchable content
|
|
var item1 = new FeedItem.with_values(
|
|
"item_1",
|
|
"Swift Programming Guide",
|
|
"https://example.com/swift",
|
|
"Learn Swift programming language basics",
|
|
"A comprehensive guide to Swift",
|
|
"Apple Developer",
|
|
"2024-01-01T12:00:00Z",
|
|
null,
|
|
null,
|
|
null, null, null, null,
|
|
"sub_1" // subscription_id
|
|
);
|
|
|
|
var item2 = new FeedItem.with_values(
|
|
"item_2",
|
|
"Python for Data Science",
|
|
"https://example.com/python",
|
|
"Data analysis with Python and pandas",
|
|
"Complete Python data science tutorial",
|
|
"Data Team",
|
|
"2024-01-02T12:00:00Z",
|
|
null,
|
|
null,
|
|
null, null, null, null,
|
|
"sub_1" // subscription_id
|
|
);
|
|
|
|
item_store.add(item1);
|
|
item_store.add(item2);
|
|
|
|
// Test FTS search (returns SearchResult)
|
|
var results = item_store.search("swift");
|
|
if (results.length != 1) {
|
|
printerr("FAIL: Expected 1 result for 'swift', got %d\n", results.length);
|
|
return;
|
|
}
|
|
if (results[0].title != "Swift Programming Guide") {
|
|
printerr("FAIL: Expected 'Swift Programming Guide', got '%s'\n", results[0].title);
|
|
return;
|
|
}
|
|
|
|
results = item_store.search("python");
|
|
if (results.length != 1) {
|
|
printerr("FAIL: Expected 1 result for 'python', got %d\n", results.length);
|
|
return;
|
|
}
|
|
if (results[0].title != "Python for Data Science") {
|
|
printerr("FAIL: Expected 'Python for Data Science', got '%s'\n", results[0].title);
|
|
return;
|
|
}
|
|
|
|
// Test fuzzy search
|
|
results = item_store.search_fuzzy("swif");
|
|
if (results.length != 1) {
|
|
printerr("FAIL: Expected 1 result for fuzzy 'swif', got %d\n", results.length);
|
|
return;
|
|
}
|
|
|
|
print("PASS: test_fts_search\n");
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
}
|
|
|
|
private void cleanup() {
|
|
if (db != null) {
|
|
db.close();
|
|
db = null;
|
|
}
|
|
|
|
// Clean up test database
|
|
if (test_db_path != null && test_db_path.length > 0) {
|
|
var file = File.new_for_path(test_db_path);
|
|
if (file.query_exists()) {
|
|
try {
|
|
file.delete();
|
|
} catch (Error e) {
|
|
warning("Failed to delete test database: %s", e.message);
|
|
}
|
|
}
|
|
|
|
// Clean up WAL file
|
|
var wal_file = File.new_for_path(test_db_path + "-wal");
|
|
if (wal_file.query_exists()) {
|
|
try {
|
|
wal_file.delete();
|
|
} catch (Error e) {
|
|
warning("Failed to delete WAL file: %s", e.message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void run_search_service() {
|
|
try {
|
|
test_db_path = "/tmp/rssuper_test_%d.db".printf((int)new DateTime.now_local().to_unix());
|
|
db = new Database(test_db_path);
|
|
} catch (DBError e) {
|
|
warning("Failed to create test database: %s", e.message);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create subscription
|
|
var sub_store = new SubscriptionStore(db);
|
|
var subscription = new FeedSubscription.with_values(
|
|
"sub_1", "https://example.com/feed.xml", "Example Feed"
|
|
);
|
|
sub_store.add(subscription);
|
|
|
|
// Add test items
|
|
var item_store = new FeedItemStore(db);
|
|
var item1 = new FeedItem.with_values(
|
|
"item_1",
|
|
"Introduction to Rust Programming",
|
|
"https://example.com/rust",
|
|
"Learn Rust programming language",
|
|
"Complete Rust tutorial for beginners",
|
|
"Rust Team",
|
|
"2024-01-01T12:00:00Z",
|
|
null,
|
|
null,
|
|
null, null, null, null,
|
|
"sub_1"
|
|
);
|
|
|
|
var item2 = new FeedItem.with_values(
|
|
"item_2",
|
|
"Advanced Rust Patterns",
|
|
"https://example.com/rust-advanced",
|
|
"Advanced Rust programming patterns",
|
|
"Deep dive into Rust patterns and best practices",
|
|
"Rust Team",
|
|
"2024-01-02T12:00:00Z",
|
|
null,
|
|
null,
|
|
null, null, null, null,
|
|
"sub_1"
|
|
);
|
|
|
|
item_store.add(item1);
|
|
item_store.add(item2);
|
|
|
|
// Test search service
|
|
var search_service = new SearchService(db);
|
|
|
|
// Perform search
|
|
var results = search_service.search("rust");
|
|
if (results.length != 2) {
|
|
printerr("FAIL: Expected 2 results for 'rust', got %d\n", results.length);
|
|
return;
|
|
}
|
|
|
|
// Check history
|
|
var history = search_service.get_history();
|
|
if (history.length != 1) {
|
|
printerr("FAIL: Expected 1 history entry, got %d\n", history.length);
|
|
return;
|
|
}
|
|
if (history[0].query != "rust") {
|
|
printerr("FAIL: Expected query 'rust', got '%s'\n", history[0].query);
|
|
return;
|
|
}
|
|
|
|
// Test fuzzy search
|
|
results = search_service.search("rus");
|
|
if (results.length != 2) {
|
|
printerr("FAIL: Expected 2 results for fuzzy 'rus', got %d\n", results.length);
|
|
return;
|
|
}
|
|
|
|
// Test suggestions
|
|
var suggestions = search_service.get_suggestions("rust");
|
|
if (suggestions.length == 0) {
|
|
printerr("FAIL: Expected at least 1 suggestion for 'rust'\n");
|
|
return;
|
|
}
|
|
|
|
print("PASS: test_search_service\n");
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
}
|
|
|
|
public void run_bookmark_store() {
|
|
try {
|
|
test_db_path = "/tmp/rssuper_test_%d.db".printf((int)new DateTime.now_local().to_unix());
|
|
db = new Database(test_db_path);
|
|
} catch (DBError e) {
|
|
warning("Failed to create test database: %s", e.message);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create subscription
|
|
var sub_store = new SubscriptionStore(db);
|
|
var subscription = new FeedSubscription.with_values(
|
|
"sub_1", "https://example.com/feed.xml", "Example Feed"
|
|
);
|
|
sub_store.add(subscription);
|
|
|
|
// Add test item
|
|
var item_store = new FeedItemStore(db);
|
|
var item = new FeedItem.with_values(
|
|
"item_1",
|
|
"Test Article",
|
|
"https://example.com/test",
|
|
"Test description",
|
|
"Test content",
|
|
"Test Author",
|
|
"2024-01-01T12:00:00Z",
|
|
null,
|
|
null,
|
|
null, null, null, null,
|
|
"sub_1"
|
|
);
|
|
item_store.add(item);
|
|
|
|
// Test bookmark store
|
|
var bookmark_store = new BookmarkStore(db);
|
|
|
|
// Create bookmark
|
|
var bookmark = new Bookmark.with_values(
|
|
"bookmark_1",
|
|
"item_1",
|
|
"Test Article",
|
|
"https://example.com/test",
|
|
"Test description",
|
|
"Test content",
|
|
"2024-01-01T12:00:00Z",
|
|
"test,important"
|
|
);
|
|
|
|
// Add bookmark
|
|
bookmark_store.add(bookmark);
|
|
|
|
// Get bookmark by ID
|
|
var retrieved = bookmark_store.get_by_id("bookmark_1");
|
|
if (retrieved == null) {
|
|
printerr("FAIL: Expected bookmark to exist after add\n");
|
|
return;
|
|
}
|
|
if (retrieved.title != "Test Article") {
|
|
printerr("FAIL: Expected title 'Test Article', got '%s'\n", retrieved.title);
|
|
return;
|
|
}
|
|
|
|
// Get all bookmarks
|
|
var all = bookmark_store.get_all();
|
|
if (all.length != 1) {
|
|
printerr("FAIL: Expected 1 bookmark, got %d\n", all.length);
|
|
return;
|
|
}
|
|
|
|
// Get bookmark count
|
|
var count = bookmark_store.count();
|
|
if (count != 1) {
|
|
printerr("FAIL: Expected count 1, got %d\n", count);
|
|
return;
|
|
}
|
|
|
|
// Get bookmarks by tag
|
|
var tagged = bookmark_store.get_by_tag("test");
|
|
if (tagged.length != 1) {
|
|
printerr("FAIL: Expected 1 bookmark by tag 'test', got %d\n", tagged.length);
|
|
return;
|
|
}
|
|
|
|
// Update bookmark
|
|
retrieved.tags = "updated,important";
|
|
bookmark_store.update(retrieved);
|
|
|
|
// Delete bookmark
|
|
bookmark_store.delete("bookmark_1");
|
|
|
|
// Verify deletion
|
|
var deleted = bookmark_store.get_by_id("bookmark_1");
|
|
if (deleted != null) {
|
|
printerr("FAIL: Expected bookmark to be deleted\n");
|
|
return;
|
|
}
|
|
|
|
// Check count after deletion
|
|
count = bookmark_store.count();
|
|
if (count != 0) {
|
|
printerr("FAIL: Expected count 0 after delete, got %d\n", count);
|
|
return;
|
|
}
|
|
|
|
print("PASS: test_bookmark_store\n");
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
}
|
|
|
|
public static int main(string[] args) {
|
|
print("Running database tests...\n");
|
|
|
|
var tests = new DatabaseTests();
|
|
|
|
print("\n=== Running subscription CRUD tests ===");
|
|
tests.run_subscription_crud();
|
|
|
|
print("\n=== Running subscription list tests ===");
|
|
tests.run_subscription_list();
|
|
|
|
print("\n=== Running feed item CRUD tests ===");
|
|
tests.run_feed_item_crud();
|
|
|
|
print("\n=== Running feed item batch tests ===");
|
|
tests.run_feed_item_batch();
|
|
|
|
print("\n=== Running search history tests ===");
|
|
tests.run_search_history();
|
|
|
|
print("\n=== Running FTS search tests ===");
|
|
tests.run_fts_search();
|
|
|
|
print("\n=== Running search service tests ===");
|
|
tests.run_search_service();
|
|
|
|
print("\n=== Running bookmark store tests ===");
|
|
tests.run_bookmark_store();
|
|
|
|
print("\nAll tests completed!\n");
|
|
return 0;
|
|
}
|
|
}
|