Skip to content
Open
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
4 changes: 2 additions & 2 deletions csv_file_update_from_upload_section/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.testsigma.addons</groupId>
<artifactId>csv_file_update_from_upload_section</artifactId>
<version>1.0.1</version>
<version>1.0.5</version>
<packaging>jar</packaging>

<properties>
Expand Down Expand Up @@ -75,7 +75,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
<version>3.17.0</version>
<scope>compile</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package com.testsigma.addons.web;


import com.testsigma.sdk.ApplicationType;
import com.testsigma.sdk.WebAction;
import com.testsigma.sdk.annotation.Action;
import com.testsigma.sdk.annotation.TestData;
import lombok.Data;
import com.opencsv.CSVReader;
import com.opencsv.CSVWriter;
import com.opencsv.exceptions.CsvException;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.net.URL;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.openqa.selenium.NoSuchElementException;

@Data
@Action(actionText = "clear cell value from a CSV file file-path where row is row-number and column is column-number",
description = "Deletes a particular cell from CSV file uses 1-based indexing for row and column numbers.",
applicationType = ApplicationType.WEB)
public class DeleteRowContentFromCsv extends WebAction {

@TestData(reference = "file-path")
private com.testsigma.sdk.TestData filePathOrUpload;

@TestData(reference = "row-number")
private com.testsigma.sdk.TestData rowNumber;

@TestData(reference = "column-number")
private com.testsigma.sdk.TestData columnNumber;

@Override
public com.testsigma.sdk.Result execute() throws NoSuchElementException {
logger.info("Initiating execution");

com.testsigma.sdk.Result result = com.testsigma.sdk.Result.SUCCESS;

String filePathString = filePathOrUpload.getValue().toString();
int targetRow;
int targetColumn;
try {
targetRow = Integer.parseInt(rowNumber.getValue().toString());
targetColumn = Integer.parseInt(columnNumber.getValue().toString());
} catch (NumberFormatException e) {
setErrorMessage("Row number and Column number must be valid integers.");
return com.testsigma.sdk.Result.FAILED;
}

// Validate 1-based input (must be at least 1)
if (targetRow < 1 || targetColumn < 1) {
setErrorMessage("Row and Column numbers must be at least 1. Given: row=" + targetRow + ", column=" + targetColumn);
return com.testsigma.sdk.Result.FAILED;
}

// Store original 1-based values for user-friendly messages
int originalRow = targetRow;
int originalColumn = targetColumn;

// Convert to 0-based index (user gives 1,1 → maps to 0,0 in CSV)
int rowIndex = targetRow - 1;
int columnIndex = targetColumn - 1;


String csvFilePath;
if (filePathString.startsWith("https://") || filePathString.startsWith("http://")) {
File tempFile = urlToCSVFileConverter("csvFileName", filePathString);
csvFilePath = tempFile.getAbsolutePath();
} else {
// Use local file path directly
logger.info("Given is local file path...");
csvFilePath = filePathString;
}
logger.info("CSV File path: " + csvFilePath);

try (Reader reader = new FileReader(csvFilePath);
CSVReader csvReader = new CSVReader(reader)) {
List<String[]> rows = csvReader.readAll();

// Check row bounds using 0-based index
if (rowIndex >= 0 && rowIndex < rows.size()) {
logger.info("Accessing row " + originalRow);
String[] row = rows.get(rowIndex);

// Check column bounds using 0-based index
if (columnIndex >= 0 && columnIndex < row.length) {
row[columnIndex] = "";

// Write the modified CSV back to the same file
try (CSVWriter writer = new CSVWriter(new FileWriter(csvFilePath))) {
writer.writeAll(rows);
} catch (IOException e) {
logger.warn("Error writing to CSV file: " + ExceptionUtils.getStackTrace(e));
setErrorMessage("Error writing to CSV file: " + e.getMessage());
result = com.testsigma.sdk.Result.FAILED;
return result;
}
Comment on lines +97 to +104
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential data loss if concurrent modifications occur.

When processing a URL-based CSV, the downloaded temp file is modified and written back to the same temp file. For local file paths, writing back to the original file could cause data corruption if multiple actions run concurrently or if the process crashes mid-write.

Consider using the same pattern as DeleteRowContentFromCsvAndStorePath and WriteCsvFileandStorePath, which create a separate temporary file for the modified content and then copy it back atomically using Files.copy with StandardCopyOption.REPLACE_EXISTING.

🤖 Prompt for AI Agents
In
csv_file_update_from_upload_section/src/main/java/com/testsigma/addons/web/DeleteRowContentFromCsv.java
around lines 97 to 104, the current code writes modified CSV rows directly back
to the same file which risks data loss/corruption on concurrent runs or crashes;
change the logic to write to a new temporary file (use Files.createTempFile or
the same temp pattern used in DeleteRowContentFromCsvAndStorePath and
WriteCsvFileandStorePath), close the writer, then atomically replace the
original target file by copying the temp file to the original path with
Files.copy(tempPath, originalPath, StandardCopyOption.REPLACE_EXISTING); ensure
proper try-with-resources, exception handling, and cleanup of the temp file in
finally so partial writes do not corrupt the original.


logger.info("Content deleted from row " + originalRow + " and column " + originalColumn);
setSuccessMessage("Content deleted from row " + originalRow + " and column " + originalColumn);
} else {
logger.warn("Column number " + originalColumn + " is out of bounds.");
setErrorMessage("Column number " + originalColumn + " is out of bounds.");
result = com.testsigma.sdk.Result.FAILED;
}
} else {
logger.warn("Row number " + originalRow + " is out of bounds.");
setErrorMessage("Row number " + originalRow + " is out of bounds.");
result = com.testsigma.sdk.Result.FAILED;
}
} catch (IOException | CsvException e) {
logger.warn("Error processing CSV file: " + ExceptionUtils.getStackTrace(e));
setErrorMessage("Error processing CSV file: " + e.getMessage());
result = com.testsigma.sdk.Result.FAILED;
return result;
}
return result;
}

public File urlToCSVFileConverter(String fileName, String url) {
try {
logger.info("Given is URL... File name: " + fileName);
URL urlObject = new URL(url);

// Extract file extension if present
String baseName = fileName;
String extension = ".csv"; // default csv format
int lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex > 0) {
baseName = fileName.substring(0, lastDotIndex);
extension = fileName.substring(lastDotIndex);
}

File tempFile = File.createTempFile(baseName, extension);
FileUtils.copyURLToFile(urlObject, tempFile);
logger.info("CSV file created: " + tempFile.getAbsolutePath());
return tempFile;
} catch (Exception e) {
logger.info("Error while accessing: " + url);
throw new RuntimeException("Unable to access or validate the CSV file. Please check the inputs.", e);
}
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package com.testsigma.addons.web;

import com.opencsv.CSVReader;
import com.opencsv.CSVWriter;
import com.opencsv.exceptions.CsvException;
import com.testsigma.sdk.ApplicationType;
import com.testsigma.sdk.WebAction;
import com.testsigma.sdk.annotation.Action;
import com.testsigma.sdk.annotation.RunTimeData;
import com.testsigma.sdk.annotation.TestData;
import lombok.Data;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.openqa.selenium.NoSuchElementException;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.List;

@Data
@Action(actionText = "clear cell value from CSV file test_data where row is row-number and column column-number" +
" and store filepath in runtime variable variable-name (It supports file from upload section)",
description = "Deletes content from a particular cell in CSV file using 1-based indexing for row " +
"and column numbers. Can accept local file paths or URLs for the CSV file." +
" Stores the file path in a runtime variable. It supports file from upload section.",
applicationType = ApplicationType.WEB)
public class DeleteRowContentFromCsvAndStorePath extends WebAction {

@TestData(reference = "row-number")
private com.testsigma.sdk.TestData rowNumber;

@TestData(reference = "column-number")
private com.testsigma.sdk.TestData columnNumber;

@TestData(reference = "test_data")
private com.testsigma.sdk.TestData filePath;

@TestData(reference = "variable-name", isRuntimeVariable = true)
private com.testsigma.sdk.TestData variableName;

@RunTimeData
private com.testsigma.sdk.RunTimeData runTimeData;

@Override
public com.testsigma.sdk.Result execute() throws NoSuchElementException {
logger.info("Initiating execution");

com.testsigma.sdk.Result result = com.testsigma.sdk.Result.SUCCESS;

String filePathString = filePath.getValue().toString();

int targetRow;
int targetColumn;
try {
targetRow = Integer.parseInt(rowNumber.getValue().toString());
targetColumn = Integer.parseInt(columnNumber.getValue().toString());
} catch (NumberFormatException e) {
setErrorMessage("Row number and Column number must be valid integers.");
return com.testsigma.sdk.Result.FAILED;
}

// Validate 1-based input (must be at least 1)
if (targetRow < 1 || targetColumn < 1) {
setErrorMessage("Row and Column numbers must be at least 1. Given: row=" + targetRow + ", column=" + targetColumn);
return com.testsigma.sdk.Result.FAILED;
}

// Store original 1-based values for user-friendly messages
int originalRow = targetRow;
int originalColumn = targetColumn;

File csvFile = null;
File tempCsvFile = null;

try {
csvFile = convertToFile(filePathString);
logger.info("CSV file path: " + csvFile.getAbsolutePath());

if (csvFile == null || !csvFile.exists()) {
setErrorMessage("CSV File not found or could not be downloaded: " + filePathString);
return com.testsigma.sdk.Result.FAILED;
}

// Create a unique temp file each time
String uniqueFileName = "updated_" + System.currentTimeMillis() + ".csv";
tempCsvFile = new File(csvFile.getParentFile(), uniqueFileName);
logger.info("Temp file path: " + tempCsvFile.getAbsolutePath());
Files.copy(csvFile.toPath(), tempCsvFile.toPath(), StandardCopyOption.REPLACE_EXISTING);

CSVReader csvReader = null;
CSVWriter writer = null;
try {
csvReader = new CSVReader(new FileReader(tempCsvFile));
List<String[]> data = csvReader.readAll();

// Convert from 1-based (user input) to 0-based index (user gives 1,1 → maps to 0,0)
int rowIndex = targetRow - 1;
int columnIndex = targetColumn - 1;

// Check row bounds using 0-based index
if (rowIndex < data.size()) {
logger.info("Accessing row " + originalRow);
String[] row = data.get(rowIndex);

// Check column bounds using 0-based index
if (columnIndex < row.length) {
row[columnIndex] = "";

writer = new CSVWriter(new FileWriter(tempCsvFile), ',', CSVWriter.NO_QUOTE_CHARACTER,
CSVWriter.DEFAULT_ESCAPE_CHARACTER, CSVWriter.DEFAULT_LINE_END);
writer.writeAll(data);
writer.flush();

logger.info("Content deleted from row " + originalRow + " and column " + originalColumn);
} else {
setErrorMessage("Column number " + originalColumn + " is out of bounds. Max columns: " + row.length);
return com.testsigma.sdk.Result.FAILED;
}
} else {
setErrorMessage("Row number " + originalRow + " is out of bounds. Max rows: " + data.size());
return com.testsigma.sdk.Result.FAILED;
}
} catch (IOException | CsvException e) {
result = com.testsigma.sdk.Result.FAILED;
setErrorMessage("Error processing CSV file: " + e.getMessage());
logger.warn("Error processing CSV file: " + e);
return result;
} finally {
if (csvReader != null) {
try {
csvReader.close();
} catch (IOException e) {
logger.warn("Error closing CSVReader: " + e.getMessage() + e);
}
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
logger.warn("Error closing CSVWriter: " + e.getMessage() + e);
}
}
}

// Copy the temp file back to the original file
try {
Files.copy(tempCsvFile.toPath(), csvFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
logger.info("Successfully copied content to original file: " + csvFile.getAbsolutePath());
} catch (IOException ex) {
logger.warn("Error copying data from temp file to original file: " + ex);
setErrorMessage("Failed to copy data from temp file to original file: " + ex.getMessage());
return com.testsigma.sdk.Result.FAILED;
}

// Store the path of the updated file
runTimeData.setKey(variableName.getValue().toString());
runTimeData.setValue(csvFile.getAbsolutePath());

setSuccessMessage("Content deleted from row " + originalRow + ", column " + originalColumn
+ ". File path stored in runtime variable: " + variableName.getValue().toString()
+ " = " + csvFile.getAbsolutePath());
} catch (Exception e) {
result = com.testsigma.sdk.Result.FAILED;
setErrorMessage("Operation Failed: " + e.getMessage());
logger.warn("Error during CSV processing: " + e.getMessage() + e);
}

return result;
}

private File convertToFile(String pathOrUrl) throws IOException {
if (pathOrUrl.startsWith("https://") || pathOrUrl.startsWith("http://")) {
// Extract the original file name from the URL
String originalFileName = FilenameUtils.getName(new URL(pathOrUrl).getPath());
// Generate a unique file name by appending a timestamp
String uniqueFileName = "temp_" + System.currentTimeMillis() + "_" + originalFileName;
logger.info("Given is a URL... Original file name: " + originalFileName + ", Unique file name: "
+ uniqueFileName);

// Create the full path for the temporary file
String filePath = String.format("%s%s%s", FileUtils.getTempDirectoryPath(), File.separator, uniqueFileName);
File tempFile = new File(filePath);

// Download the file from the URL to the temporary location
FileUtils.copyURLToFile(new URL(pathOrUrl), tempFile, 10000, 10000);
logger.info("Temp file created for URL file: " + uniqueFileName + " at path " + filePath);

return tempFile;
} else {
logger.info("Given is a local file path...");
return new File(pathOrUrl);
}
}
Comment on lines +176 to +198
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Consolidate convertToFile with similar methods and add temp file cleanup.

The convertToFile method is duplicated in WriteCsvFileandStorePath. Additionally, this method is functionally similar to urlToCSVFileConverter used in other CSV action classes, though with slightly different implementation details.

  1. Extract this method to a shared utility class to eliminate duplication
  2. Add tempFile.deleteOnExit() after line 187 to clean up downloaded files
  3. Consider adding connection/read timeouts to FileUtils.copyURLToFile (note: this file already has 10-second timeouts on line 190, which is good)
🤖 Prompt for AI Agents
In
csv_file_update_from_upload_section/src/main/java/com/testsigma/addons/web/DeleteRowContentFromCsvAndStorePath.java
around lines 176 to 198, the convertToFile method is duplicated elsewhere and
lacks automatic cleanup of downloaded temp files; extract this logic into a
shared utility class (e.g., CsvFileUtils or FileDownloadUtils) and replace
duplicate implementations in WriteCsvFileandStorePath and other CSV action
classes with calls to the new utility, add tempFile.deleteOnExit() immediately
after creating the tempFile (after line 187) so downloaded files are scheduled
for removal, and keep the existing FileUtils.copyURLToFile call with the 10s
connect/read timeouts (or make them configurable in the utility) to avoid
blocking.

}

Loading