diff --git a/app/src/main/java/ru/project/io/StudentCsvReader.java b/app/src/main/java/ru/project/io/StudentCsvReader.java index a6b93c4..8dc2bb5 100644 --- a/app/src/main/java/ru/project/io/StudentCsvReader.java +++ b/app/src/main/java/ru/project/io/StudentCsvReader.java @@ -14,70 +14,76 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import ru.project.student.Student; import ru.project.student.StudentBuilder; - -public class StudentCsvReader implements Iterator { + public class StudentCsvReader implements Iterator { private static final String GROUP_NUMBER = "groupNumber"; private static final String AVERAGE_GRADE = "averageGrade"; private static final String RECORD_BOOK_NUMBER = "recordBookNumber"; - private final CSVParser parser; private final Iterator records; - - private int lineOfFile; - - public StudentCsvReader(Path path2) { - lineOfFile = 0; - Path path = Paths.get(path2); - if (!path.isAbsolute()) { - throw new IllegalArgumentException("File path must be absolute"); + private int lineOfFile=0; + public StudentCsvReader(Path path) { + if (!path.isAbsolute()) { + throw new IllegalArgumentException("File path must be absolute"); + } + try { + CSVFormat format = CSVFormat.DEFAULT.builder() + .setHeader() + .setSkipHeaderRecord(true) + .build(); + this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); + validateHeader(parser.getHeaderMap()); + this.records = parser.iterator(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } - - try { - CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).get(); - this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); - validateHeader(parser.getHeaderMap()); - this.records = parser.iterator(); - } catch (IOException e) { - throw new UncheckedIOException(e); + @Override + public boolean hasNext() { + return records.hasNext(); } - } - - @Override - public boolean hasNext() { - return records.hasNext(); - } - - @Override - public Student next() { - lineOfFile++; - if (!records.hasNext()) { - throw new NoSuchElementException("Problem at line " + lineOfFile + "."); - } - - CSVRecord record = records.next(); - if (record.size() != 3) { - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ".Expected 3 columns, but got " + record.size()); - } - try { - double averageGrade = Double.parseDouble(record.get(AVERAGE_GRADE)); - int recordBookNumber = Integer.parseInt(record.get(RECORD_BOOK_NUMBER)); + @Override + public Student next() { + if (!records.hasNext()) { + throw new NoSuchElementException("No more records at line " + (lineOfFile + 1)); + } + lineOfFile++; + CSVRecord record = records.next(); + if (record.size() != 3) { + throw new IllegalArgumentException( + "Problem at line " + lineOfFile + ". Expected 3 columns, but got " + record.size()); + } + String groupNumber = record.get(GROUP_NUMBER); + String averageGradeStr = record.get(AVERAGE_GRADE); + String recordBookNumberStr = record.get(RECORD_BOOK_NUMBER); + double averageGrade; + int recordBookNumber; + try { + averageGrade = Double.parseDouble(averageGradeStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Problem at line " + lineOfFile + ": cannot parse averageGrade '" + averageGradeStr + "' as double", e); + } + try { + recordBookNumber = Integer.parseInt(recordBookNumberStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Problem at line " + lineOfFile + ": cannot parse recordBookNumber '" + recordBookNumberStr + "' as int", e); + } return new StudentBuilder() - .setGroupNumber(record.get(GROUP_NUMBER)) - .setAverageGrade(averageGrade) - .setRecordBookNumber(recordBookNumber) - .build(); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ". Unacceptable type convertation"); + .setGroupNumber(groupNumber) + .setAverageGrade(averageGrade) + .setRecordBookNumber(recordBookNumber) + .build(); + } + @Override + public void close() throws IOException { + parser.close(); + } + private static void validateHeader(Map headerMap) { + String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; + if (headerMap.size() != expected.length + || !headerMap.keySet().containsAll(Arrays.asList(expected))) { + throw new IllegalArgumentException("Invalid CSV header"); + } } } - - private static void validateHeader(Map headerMap) { - String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; - if (headerMap.size() != expected.length - || !headerMap.keySet().containsAll(Arrays.asList(expected))) { - throw new IllegalArgumentException("Invalid CSV header"); - } - } -} diff --git a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java index 1ca6657..ed1bb02 100644 --- a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java +++ b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java @@ -1,10 +1,8 @@ package ru.project.io; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -15,167 +13,104 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import ru.project.student.Student; - +import ru.project.collection.MyLinkedList; +import ru.project.collection.MyList; +import ru.project.collectors.MyCollectors; +import ru.project.collectors.MyListCollector; class StudentCsvReaderTest { - @TempDir Path tempDir; - private Path studentPath; + @TempDir + Path tempDir; + @Test + void shouldTestreadValidCsvIntoMyList() throws IOException { + Path csv = tempDir.resolve("students.csv"); + String content = String.join("\n", + "groupNumber,averageGrade,recordBookNumber", + "M10,4.5,12345", // валидно: 1 буква + 2 цифры + "B05,3.8,12346" // валидно + ); + Files.writeString(csv, content, StandardCharsets.UTF_8); + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + var studentStream = StreamSupport.stream( + Spliterators.spliteratorUnknownSize(reader, 0), + false + ); + MyList students = studentStream + .collect(MyCollectors.toMyList()); + assertEquals(2, students.size()); + assertEquals("M10", students.get(0).getGroupNumber()); + assertEquals(4.5, students.get(0).getAverageGrade(), 0.0); + assertEquals(12345, students.get(0).getRecordBookNumber()); + assertEquals("B05", students.get(1).getGroupNumber()); + assertEquals(3.8, students.get(1).getAverageGrade(), 0.0); + assertEquals(12346, students.get(1).getRecordBookNumber()); + } + } + @Test + void shouldTestinvalidHeaderThrowsException() throws IOException { + Path csv = tempDir.resolve("bad-header.csv"); + String content = """ + badGroup,badGrade,badBook + M101,4.5,12345 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + () -> new StudentCsvReader(csv.toAbsolutePath()) + ); + assertTrue(ex.getMessage().contains("Invalid CSV header")); + } + @Test + void shouldTestwrongColumnCountThrowsException() throws IOException { + Path csv = tempDir.resolve("wrong-columns.csv"); + String content = """ + groupNumber,averageGrade,recordBookNumber + M101,4.5 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + var reader = new StudentCsvReader(csv.toAbsolutePath()); + assertTrue(reader.hasNext()); + // следующая строка имеет только 2 колонки + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + reader::next + ); + assertTrue(ex.getMessage().contains("Expected 3 columns")); + } + @Test + void shouldTestparseErrorThrowsExceptionWithDetails() throws IOException { + Path csv = tempDir.resolve("parse-error.csv"); + String content = """ + groupNumber,averageGrade,recordBookNumber + M101,not-a-number,12345 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); - // Testing StudentCsvReader - - @BeforeAll - void generateStudentTestFile() { - studentPath = tempDir.resolve("students.csv"); - Files.write( - studentPath, - new String[] {"groupNumber,averageGrade,recordBookNumber", "A21,5.0,331", "A21,4.0,332"}, - StandardCharsets.UTF_8); - } - - @BeforeEach - void setUp() { - valideReader = new StudentCsvReader(studentPath.toAbsolutePath().toString()); - } - - @Test - void readerTestRejectRelativePath() { - assertThrows( - IllegalArgumentException.class, () -> new StudentCsvReader(studentPath.toString())); - } - - @Test - void readerRejectPathIsDir() { - assertThrows(UncheckedIOException.class, () -> new StudentCsvReader(tempDir.toAbsolutePath())); - } - - @Test - void readerTestFileNotExist() { - assertThrows( - UncheckedIOException.class, - () -> new StudentCsvReader(tempDir.resolve("sss.csv").toAbsolutePath().toString())); - } - - @Test - void testSkipsHeader() { - Student student = valideReader.next(); - assertNotEquals(student.getGroupNumber(), "groupNumber"); - } - - @Test - void testCreationStudentOnValidData() { - Student student = valideReader.next(); - assertEquals(student.getGroupNumber(), "A21"); - assertEquals(student.getAverageGrade(), 5.0); - assertEquals(student.getRecordBookNumber(), 331); - } - - @Test - void testNotSkippingFirstStudent() { - valideReader.hasNext(); - valideReader.hasNext(); - Student student = valideReader.next(); - assertEquals(student.getGroupNumber(), "A21"); - assertEquals(student.getAverageGrade(), 5.0); - assertEquals(student.getRecordBookNumber(), 331); - } - - @Test - void testValidEndsOfFile() { - valideReader.next(); - valideReader.next(); - assertTrue(!valideReader.hasNext()); - } - - @Test - void testOutOfIndex() { - assertThrows( - NoSuchElementException.class, - () -> { - Student student = valideReader.next(); - student = valideReader.next(); - student = valideReader.next(); - }); - } - - @Test - void readerTestRejectFewParametres() { - assertThrows( - IllegalArgumentException.class, - () -> { - Path path = tempDir.resolve("fewparameters.csv"); - Files.write( - path, - new String[] {"groupNumber,averageGrade,recordBookNumber", "A21,5.0"}, - StandardCharsets.UTF_8); - StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); - Student student = reader.next(); - }); - } - - @Test - void readerTestRejectInvalidGroupNumber() { - assertThrows( - IllegalArgumentException.class, - () -> { - Path path = tempDir.resolve("invalid_groupnumber.csv"); - Files.write( - path, - new String[] { - "groupNumber,averageGrade,recordBookNumber", "AAA,5.0,331", "A21,4.0,332" - }, - StandardCharsets.UTF_8); - StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); - Student student = reader.next(); - }); - } - - @Test - void readerTestRejectInvalidAverageGrade() { - assertThrows( - IllegalArgumentException.class, - () -> { - Path path = tempDir.resolve("invalid_averagegrade.csv"); - Files.write( - path, - new String[] { - "groupNumber,averageGrade,recordBookNumber", "A21,-5.0,331", "A21,4.0,332" - }, - StandardCharsets.UTF_8); - StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); - Student student = reader.next(); - }); - } - - @Test - void readerTestRejectInvalidRecordBookNumber() { - assertThrows( - IllegalArgumentException.class, - () -> { - Path path = tempDir.resolve("invalid_recordbooknumber.csv"); - Files.write( - path, - new String[] { - "groupNumber,averageGrade,recordBookNumber", "A21,5.0,-331", "A21,4.0,332" - }, - StandardCharsets.UTF_8); - StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); - Student student = reader.next(); - }); - } - - @Test - void testInvalidHeader() { - assertThrows( - IllegalArgumentException.class, - () -> { - Path path = tempDir.resolve("invalid_header.csv"); - Files.write( - path, - new String[] { - "groupNumber,averageGrade,recordBookNomber", "A21,5.0,-331", "A21,4.0,332" - }, - StandardCharsets.UTF_8); - StudentCsvReader reader = new StudentCsvReader(path.toAbsolutePath().toString()); - Student student = reader.next(); - }); - } + var reader = new StudentCsvReader(csv.toAbsolutePath()); + assertTrue(reader.hasNext()); + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + reader::next + ); + assertTrue(ex.getMessage().contains("cannot parse averageGrade")); + } + @Test + void shouldTestemptyDataAfterHeaderThrowsNoSuchElement() throws IOException { + Path csv = tempDir.resolve("empty-data.csv"); + String content = """ + groupNumber,averageGrade,recordBookNumber + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + var reader = new StudentCsvReader(csv.toAbsolutePath()); + assertFalse(reader.hasNext()); + assertThrows(NoSuchElementException.class, reader::next); + } + @Test + void shouldTestrelativePathThrowsException() { + Path relative = Path.of("relative-path.csv"); + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + () -> new StudentCsvReader(relative) + ); + assertTrue(ex.getMessage().contains("absolute")); + } }