Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Context
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import com.opencsv.CSVWriter
import com.yogeshpaliyal.deepr.Deepr
import com.yogeshpaliyal.deepr.DeeprQueries
import com.yogeshpaliyal.deepr.R
Expand Down Expand Up @@ -83,14 +84,27 @@ class ExportRepositoryImpl(
data: List<Deepr>,
) {
outputStream.bufferedWriter().use { writer ->
// Write Header
writer.write(
"${Constants.Header.LINK},${Constants.Header.CREATED_AT},${Constants.Header.OPENED_COUNT},${Constants.Header.NAME}\n",
)
// Write Data
data.forEach { item ->
val row = "${item.link},${item.createdAt},${item.openedCount},${item.name}\n"
writer.write(row)
CSVWriter(writer).use { csvWriter ->
// Write Header
csvWriter.writeNext(
arrayOf(
Constants.Header.LINK,
Constants.Header.CREATED_AT,
Constants.Header.OPENED_COUNT,
Constants.Header.NAME,
),
)
// Write Data
data.forEach { item ->
csvWriter.writeNext(
arrayOf(
item.link,
item.createdAt.toString(),
item.openedCount.toString(),
item.name,
),
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,21 @@ class ImportRepositoryImpl(
// verify header first
val header = csvReader.readNext()
if (header == null ||
header.size < 3 ||
header.size < 4 ||
header[0] != Constants.Header.LINK ||
header[1] != Constants.Header.CREATED_AT ||
header[2] != Constants.Header.OPENED_COUNT
header[2] != Constants.Header.OPENED_COUNT ||
header[3] != Constants.Header.NAME
) {
return RequestResult.Error("Invalid CSV header")
}

csvReader.forEach { row ->
if (row.size >= 3) {
if (row.size >= 4) {
val link = row[0]
val openedCount = row[2].toLongOrNull() ?: 0L
val name = row[3].toString()
// Name is everything from index 3 onwards, joined if split across columns
val name = row.drop(3).joinToString(",")
val existing = deeprQueries.getDeeprByLink(link).executeAsOneOrNull()
if (link.isNotBlank() && existing == null) {
updatedCount++
Expand Down
114 changes: 114 additions & 0 deletions app/src/test/java/com/yogeshpaliyal/deepr/CsvExportImportTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.yogeshpaliyal.deepr

import com.opencsv.CSVReader
import com.opencsv.CSVWriter
import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.StringReader
import java.io.StringWriter

/**
* Test for CSV export/import functionality, specifically handling commas in names
*/
class CsvExportImportTest {
@Test
fun csvWriter_handlesCommasInValues() {
// Test that CSVWriter properly escapes commas
val stringWriter = StringWriter()
val csvWriter = CSVWriter(stringWriter)

csvWriter.writeNext(arrayOf("Link", "CreatedAt", "OpenedCount", "Name"))
csvWriter.writeNext(arrayOf("https://example.com", "1234567890", "5", "Test, with comma"))
csvWriter.writeNext(arrayOf("https://test.com", "9876543210", "10", "Normal name"))
csvWriter.close()

val output = stringWriter.toString()

// Verify that the name with comma is properly quoted
assert(output.contains("\"Test, with comma\""))

// Now test that CSVReader can parse it back correctly
val csvReader = CSVReader(StringReader(output))
val rows = csvReader.readAll()

assertEquals(3, rows.size) // Header + 2 data rows
assertEquals("Name", rows[0][3])
assertEquals("Test, with comma", rows[1][3])
assertEquals("Normal name", rows[2][3])
}

@Test
fun csvWriter_handlesQuotesInValues() {
// Test that CSVWriter properly escapes quotes
val stringWriter = StringWriter()
val csvWriter = CSVWriter(stringWriter)

csvWriter.writeNext(arrayOf("Link", "CreatedAt", "OpenedCount", "Name"))
csvWriter.writeNext(arrayOf("https://example.com", "1234567890", "5", "Test \"quoted\" name"))
csvWriter.close()

val output = stringWriter.toString()

// Now test that CSVReader can parse it back correctly
val csvReader = CSVReader(StringReader(output))
val rows = csvReader.readAll()

assertEquals(2, rows.size) // Header + 1 data row
assertEquals("Test \"quoted\" name", rows[1][3])
}

@Test
fun csvWriter_handlesNewlinesInValues() {
// Test that CSVWriter properly escapes newlines
val stringWriter = StringWriter()
val csvWriter = CSVWriter(stringWriter)

csvWriter.writeNext(arrayOf("Link", "CreatedAt", "OpenedCount", "Name"))
csvWriter.writeNext(arrayOf("https://example.com", "1234567890", "5", "Test\nwith newline"))
csvWriter.close()

val output = stringWriter.toString()

// Now test that CSVReader can parse it back correctly
val csvReader = CSVReader(StringReader(output))
val rows = csvReader.readAll()

assertEquals(2, rows.size) // Header + 1 data row
assertEquals("Test\nwith newline", rows[1][3])
}

@Test
fun csvReader_handlesMultipleCommasInName() {
// Test CSV data with multiple commas in the name field
val csvData = """Link,CreatedAt,OpenedCount,Name
https://example.com,1234567890,5,"First, Second, Third"
https://test.com,9876543210,10,Simple
"""

val csvReader = CSVReader(StringReader(csvData))
val rows = csvReader.readAll()

assertEquals(3, rows.size) // Header + 2 data rows
assertEquals("Name", rows[0][3])
assertEquals("First, Second, Third", rows[1][3])
assertEquals("Simple", rows[2][3])
}

@Test
fun importLogic_joinsColumnsCorrectly() {
// Test the join logic used in import when name might be split
val row1 = arrayOf("https://example.com", "1234567890", "5", "Name with comma")
val name1 = row1.drop(3).joinToString(",")
assertEquals("Name with comma", name1)

// Test with properly escaped CSV (name in single column)
val row2 = arrayOf("https://example.com", "1234567890", "5", "First, Second, Third")
val name2 = row2.drop(3).joinToString(",")
assertEquals("First, Second, Third", name2)

// Test edge case with old-style split data (shouldn't happen with new export, but handles legacy)
val row3 = arrayOf("https://example.com", "1234567890", "5", "First", " Second", " Third")
val name3 = row3.drop(3).joinToString(",")
assertEquals("First, Second, Third", name3)
}
}