diff --git a/module01/pom.xml b/module01/pom.xml index 932df7e3..46bba953 100644 --- a/module01/pom.xml +++ b/module01/pom.xml @@ -11,6 +11,14 @@ jar module01 + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + UTF-8 diff --git a/module01/src/main/java/ru/sberbank/edu/GCD.java b/module01/src/main/java/ru/sberbank/edu/GCD.java new file mode 100644 index 00000000..cfa97057 --- /dev/null +++ b/module01/src/main/java/ru/sberbank/edu/GCD.java @@ -0,0 +1,18 @@ +package ru.sberbank.edu; + +/** + * The Euclid's algorithm + * @author Aleksandr Kanarskiy + */ +public class GCD implements CommonDivisor{ + @Override + public int getDivisor(int firstNumber, int secondNumber) { + while (secondNumber !=0) { + int tmp = firstNumber%secondNumber; + firstNumber = secondNumber; + secondNumber = tmp; + } + return firstNumber; + } +} + diff --git a/module01/src/main/java/ru/sberbank/edu/GreetingImpl.java b/module01/src/main/java/ru/sberbank/edu/GreetingImpl.java new file mode 100644 index 00000000..d91aaed0 --- /dev/null +++ b/module01/src/main/java/ru/sberbank/edu/GreetingImpl.java @@ -0,0 +1,34 @@ +package ru.sberbank.edu; +/** + * About myself + * @author Aleksandr Kanarskiy + */ +public class GreetingImpl implements Greeting{ + public String getName() { + return name; + } + + public String getLastName() { + return lastName; + } + + private String name; + private String lastName; + private String hobby; + + public GreetingImpl() { + + } + public GreetingImpl(String name,String lastName,String hobby) { + this.hobby = hobby; + this.lastName = lastName; + this.name = name; + + } + + @Override + public String getBestHobby() { + return this.hobby; + } + +} diff --git a/module01/src/test/java/ru/sberbank/edu/GCDTest.java b/module01/src/test/java/ru/sberbank/edu/GCDTest.java new file mode 100644 index 00000000..0c6d5d2d --- /dev/null +++ b/module01/src/test/java/ru/sberbank/edu/GCDTest.java @@ -0,0 +1,19 @@ +package ru.sberbank.edu; + + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GCDTest { + @Test + void validateGCDTest(){ + GCD gcd = new GCD(); + assertAll( + ()->assertEquals(28,gcd.getDivisor(616,364)), + ()->assertEquals(123,gcd.getDivisor(123,0)) + ); + } + +} diff --git a/module01/src/test/java/ru/sberbank/edu/GreetingImplTest.java b/module01/src/test/java/ru/sberbank/edu/GreetingImplTest.java new file mode 100644 index 00000000..bc5d4563 --- /dev/null +++ b/module01/src/test/java/ru/sberbank/edu/GreetingImplTest.java @@ -0,0 +1,20 @@ +package ru.sberbank.edu; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GreetingImplTest { + @Test + void validateGettersTest() { + GreetingImpl greetingImpl = new GreetingImpl("Alesandr", "Kanarskiy", "Snorking"); + assertAll( + () -> assertEquals("Alesandr", greetingImpl.getName()), + () -> assertEquals("Kanarskiy", greetingImpl.getLastName()), + () -> assertEquals("Snorking", greetingImpl.getBestHobby()) + ); + + } + +} diff --git a/module02/pom.xml b/module02/pom.xml index 45e842be..3d67372f 100644 --- a/module02/pom.xml +++ b/module02/pom.xml @@ -11,6 +11,10 @@ jar module02 + + + + UTF-8 diff --git a/module02/src/input.txt b/module02/src/input.txt new file mode 100644 index 00000000..d6935b05 --- /dev/null +++ b/module02/src/input.txt @@ -0,0 +1,6 @@ +sdasdasd dsadas sadsdasd sdasda +12sdsad dsad sd sda sad +dsa s + + asda + в ыф ы ы \ No newline at end of file diff --git a/module02/src/main/java/ru/sberbank/edu/App.java b/module02/src/main/java/ru/sberbank/edu/App.java index 5419c026..8654c2e8 100644 --- a/module02/src/main/java/ru/sberbank/edu/App.java +++ b/module02/src/main/java/ru/sberbank/edu/App.java @@ -1,13 +1,39 @@ package ru.sberbank.edu; +import java.io.IOException; +import java.sql.SQLException; + /** * Hello world! * */ -public class App -{ - public static void main( String[] args ) - { - System.out.println( "Hello World!" ); +public class App { + public static void main(String[] args) { + String inputFilePath = "module02/src/input.txt"; + String outputFilePath = "module02/src/output.txt"; + + try { + // Создаем экземпляр FileResource с указанием пути к файлу входных данных + DataResource fileResource = new FileResource(inputFilePath); + + // Создаем экземпляр DataResourceProcessor с передачей ему FileResource + DataResourceProcessor processor = new DataResourceProcessor(fileResource); + + // Получаем статистику из ресурса + String statistics = processor.processResource(); + + // Записываем статистику в файл output.txt + fileResource.writeResultToFile(outputFilePath, statistics); + + // Закрываем ресурс + fileResource.close(); + + System.out.println("Статистика записана в файл " + outputFilePath); + } catch (IOException e) { + System.out.println("Ошибка при работе с файлом: " + e.getMessage()); + } catch (SQLException e) { + System.out.println("Ошибка при работе с базой данных: " + e.getMessage()); + } } + } diff --git a/module02/src/main/java/ru/sberbank/edu/DataResource.java b/module02/src/main/java/ru/sberbank/edu/DataResource.java new file mode 100644 index 00000000..a7f1632b --- /dev/null +++ b/module02/src/main/java/ru/sberbank/edu/DataResource.java @@ -0,0 +1,11 @@ +package ru.sberbank.edu; + +import java.io.IOException; +import java.sql.SQLException; + +public interface DataResource { + String readData() throws IOException, SQLException; + void close() throws IOException, SQLException; + void writeResultToFile(String outputFilePath, String statistics) throws IOException; + +} diff --git a/module02/src/main/java/ru/sberbank/edu/DataResourceProcessor.java b/module02/src/main/java/ru/sberbank/edu/DataResourceProcessor.java new file mode 100644 index 00000000..7047afd1 --- /dev/null +++ b/module02/src/main/java/ru/sberbank/edu/DataResourceProcessor.java @@ -0,0 +1,41 @@ +package ru.sberbank.edu; + +import java.io.IOException; +import java.sql.SQLException; + +public class DataResourceProcessor { + private final DataResource resource; + + public DataResourceProcessor(DataResource resource) { + this.resource = resource; + } + + public String processResource() throws IOException, SQLException { + int lineCount = 0; + String longestLine = ""; + int totalSpaces = 0; + + String line; + while ((line = resource.readData()) != null) { + lineCount++; + if (line.length() > longestLine.length()) { + longestLine = line; + } + totalSpaces += countSpaces(line); + } + + resource.close(); + return String.format("Line count: %s, longest line: %s, total spaces: %s",lineCount,longestLine,totalSpaces); + + } + + int countSpaces(String line) { + int totalSpaces = 0; + for (char c : line.toCharArray()) { + if (c == ' ') { + totalSpaces++; + } + } + return totalSpaces; + } +} diff --git a/module02/src/main/java/ru/sberbank/edu/DatabaseResource.java b/module02/src/main/java/ru/sberbank/edu/DatabaseResource.java new file mode 100644 index 00000000..7f979810 --- /dev/null +++ b/module02/src/main/java/ru/sberbank/edu/DatabaseResource.java @@ -0,0 +1,32 @@ +package ru.sberbank.edu; + +import java.sql.*; + +public class DatabaseResource extends OutputData implements DataResource { + private final Connection connection; + private final Statement statement; + private final ResultSet resultSet; + + public DatabaseResource(String url, String user, String password) throws SQLException { + connection = DriverManager.getConnection(url, user, password); + statement = connection.createStatement(); + resultSet = statement.executeQuery("SELECT * FROM my_table"); + } + + @Override + public String readData() throws SQLException { + if (resultSet.next()) { + return resultSet.getString("data"); + } + return null; + } + + @Override + public void close() throws SQLException { + resultSet.close(); + statement.close(); + connection.close(); + } + + +} diff --git a/module02/src/main/java/ru/sberbank/edu/FileResource.java b/module02/src/main/java/ru/sberbank/edu/FileResource.java new file mode 100644 index 00000000..8df1c068 --- /dev/null +++ b/module02/src/main/java/ru/sberbank/edu/FileResource.java @@ -0,0 +1,25 @@ +package ru.sberbank.edu; + +import java.io.*; + +public class FileResource extends OutputData implements DataResource { + private final BufferedReader reader; + + private final String filePath; + + public FileResource(String filePath) throws IOException { + reader = new BufferedReader(new FileReader(filePath)); + this.filePath = filePath; + } + + @Override + public String readData() throws IOException { + validateFile(filePath); + return reader.readLine(); + } + + @Override + public void close() throws IOException { + reader.close(); + } +} \ No newline at end of file diff --git a/module02/src/main/java/ru/sberbank/edu/OutputData.java b/module02/src/main/java/ru/sberbank/edu/OutputData.java new file mode 100644 index 00000000..3f85f05f --- /dev/null +++ b/module02/src/main/java/ru/sberbank/edu/OutputData.java @@ -0,0 +1,35 @@ +package ru.sberbank.edu; + +import java.io.*; +import java.sql.SQLException; + + +public abstract class OutputData implements DataResource{ + @Override + public String readData() throws IOException, SQLException { + return null; + } + + @Override + public void close() throws IOException, SQLException { + + } + public void writeResultToFile(String filePath, String content) throws IOException { + validateFile(filePath); + try( BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))){ + writer.write(content); + } + + + } + protected void validateFile(String filePath) throws FileNotFoundException { + File file = new File(filePath); + if (!file.exists()) { + throw new FileNotFoundException("File does not exist."); + } + + if (!file.isFile()) { + throw new IllegalArgumentException("Specified path does not lead to a file."); + } + } +} diff --git a/module02/src/main/java/ru/sberbank/edu/Statistic.java b/module02/src/main/java/ru/sberbank/edu/Statistic.java deleted file mode 100644 index 49ef9698..00000000 --- a/module02/src/main/java/ru/sberbank/edu/Statistic.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.sberbank.edu; - -// интерфейс можно менять -public interface Statistic { - - int getLineCount(); - int getSpaceCount(); - String getLongestLine(); - void save(int lineCount, int spaceCount, String line); - -} diff --git a/module02/src/output.txt b/module02/src/output.txt new file mode 100644 index 00000000..c5573230 --- /dev/null +++ b/module02/src/output.txt @@ -0,0 +1 @@ +Line count: 6, longest line: sdasdasd dsadas sadsdasd sdasda, total spaces: 16 \ No newline at end of file diff --git a/module02/src/test/java/ru/sberbank/edu/AppTest.java b/module02/src/test/java/ru/sberbank/edu/AppTest.java deleted file mode 100644 index 895d735c..00000000 --- a/module02/src/test/java/ru/sberbank/edu/AppTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package ru.sberbank.edu; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * Unit test for simple App. - */ -public class AppTest - extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); - } - - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } - - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } -} diff --git a/module02/src/test/java/ru/sberbank/edu/DataResourceProcessorTest.java b/module02/src/test/java/ru/sberbank/edu/DataResourceProcessorTest.java new file mode 100644 index 00000000..50a9f18c --- /dev/null +++ b/module02/src/test/java/ru/sberbank/edu/DataResourceProcessorTest.java @@ -0,0 +1,60 @@ +package ru.sberbank.edu; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DataResourceProcessorTest { + @Mock + private DataResource dataResourceMock; + @InjectMocks + private DataResourceProcessor processor; + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + } + + @Test + public void testProcessResource() throws IOException, SQLException { + // Создаем экземпляр DataResourceProcessor для тестирования метода processResource() + processor = new DataResourceProcessor(dataResourceMock); + + // Задаем ожидания для мока DataResource + String[] lines = {"hello", "world", "java"}; + Mockito.when(dataResourceMock.readData()).thenReturn(lines[0], lines[1], lines[2], null); + + // Вызываем метод processResource() + String result = processor.processResource(); + + // Проверяем, что методы мока были вызваны правильное количество раз + Mockito.verify(dataResourceMock, Mockito.times(lines.length + 1)).readData(); + Mockito.verify(dataResourceMock, Mockito.times(1)).close(); + + // Проверяем, что результат соответствует ожидаемому значению + String expected = "Line count: " + lines.length + ", longest line: " + lines[0] + ", total spaces: 0"; + assertEquals(expected, result); + } + + + @Test + public void testCountSpaces() { + // Создаем экземпляр DataResourceProcessor для тестирования метода countSpaces() + processor = new DataResourceProcessor(dataResourceMock); + + // Вызываем метод countSpaces() + int result = processor.countSpaces("hello world"); + + // Проверяем, что результат равен ожидаемому значению + int expected = 1; + assertEquals(expected, result); + } +} diff --git a/module02/src/test/java/ru/sberbank/edu/FileResourceTest.java b/module02/src/test/java/ru/sberbank/edu/FileResourceTest.java new file mode 100644 index 00000000..711caf1e --- /dev/null +++ b/module02/src/test/java/ru/sberbank/edu/FileResourceTest.java @@ -0,0 +1,34 @@ +package ru.sberbank.edu; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class FileResourceTest { + + @Mock + private FileResource fileResource; + + + @Test + void testReadData() throws IOException { + + String expectedData = "Hello, world!"; + + + when(fileResource.readData()).thenReturn(expectedData); + + String actualData = fileResource.readData(); + + assertEquals(expectedData, actualData); + + + } +} diff --git a/module02/src/test/java/ru/sberbank/edu/OutputDataTest.java b/module02/src/test/java/ru/sberbank/edu/OutputDataTest.java new file mode 100644 index 00000000..75bae47b --- /dev/null +++ b/module02/src/test/java/ru/sberbank/edu/OutputDataTest.java @@ -0,0 +1,62 @@ +package ru.sberbank.edu; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.*; + +class OutputDataTest { + + @TempDir + File tempDir; + + @Test + void testWriteToFile() throws IOException { + String filePath = tempDir.getAbsolutePath() + "/test.txt"; + String content = "Hello, World!"; + + OutputData outputData = new OutputData() { + @Override + protected void validateFile(String filePath) { + // Автомокирование, поскольку validateFile() защищенный метод и не может быть протестирован напрямую + } + }; + + outputData.writeResultToFile(filePath, content); + + assertTrue(Files.exists(Path.of(filePath))); + assertEquals(content, Files.readString(Path.of(filePath))); + } + + @Test + void testValidateFile() { + String filePath = tempDir.getAbsolutePath() + "/test.txt"; + File file = new File(filePath); // Создаем файл для проверки + + OutputData outputData = new OutputData() { + @Override + protected void validateFile(String filePath) throws FileNotFoundException { + if (!file.exists()) { + throw new FileNotFoundException("File does not exist."); + } + } + }; + + assertThrows(FileNotFoundException.class, () -> outputData.validateFile(filePath)); + + // Создаем файл для проверки и повторно вызываем validateFile() + try { + Files.createFile(Path.of(filePath)); + } catch (IOException e) { + e.printStackTrace(); + } + + assertDoesNotThrow(() -> outputData.validateFile(filePath)); + } +} diff --git a/module03/src/main/java/ru/sberbank/edu/App.java b/module03/src/main/java/ru/sberbank/edu/App.java index 5419c026..b77ea845 100644 --- a/module03/src/main/java/ru/sberbank/edu/App.java +++ b/module03/src/main/java/ru/sberbank/edu/App.java @@ -1,5 +1,8 @@ package ru.sberbank.edu; +import java.util.ArrayList; +import java.util.List; + /** * Hello world! * @@ -10,4 +13,8 @@ public static void main( String[] args ) { System.out.println( "Hello World!" ); } + + + + } diff --git a/module03/src/main/java/ru/sberbank/edu/CustomArrayImpl.java b/module03/src/main/java/ru/sberbank/edu/CustomArrayImpl.java new file mode 100644 index 00000000..760c2e1d --- /dev/null +++ b/module03/src/main/java/ru/sberbank/edu/CustomArrayImpl.java @@ -0,0 +1,159 @@ +package ru.sberbank.edu; + +import java.util.Collection; +import java.util.Arrays; + +public class CustomArrayImpl implements CustomArray { + + private static final int DEF_CAPACITY = 10; + private T[] array; + private int size; + + public CustomArrayImpl() { + this.array = (T[]) new Object[DEF_CAPACITY]; + this.size = 0; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public boolean add(T item) { + ensureCapacity(size + 1); + array[size++] = item; + return true; + } + + @Override + public boolean addAll(T[] items) { + if (items == null) + throw new IllegalArgumentException(); + ensureCapacity(size + items.length); + System.arraycopy(items, 0, array, size, items.length); + size += items.length; + return true; + } + + @Override + public boolean addAll(Collection items) { + if (items == null) + throw new IllegalArgumentException(); + return addAll((T[]) items.toArray()); + } + + @Override + public boolean addAll(int index, T[] items) { + if (index > size || index < 0) + throw new ArrayIndexOutOfBoundsException(); + if (items == null) + throw new IllegalArgumentException(); + ensureCapacity(size + items.length); + System.arraycopy(array, index, array, index + items.length, size - index); + System.arraycopy(items, 0, array, index, items.length); + size += items.length; + return true; + } + + @Override + public T get(int index) { + if (index >= size || index < 0) + throw new ArrayIndexOutOfBoundsException(); + return array[index]; + } + + @Override + public T set(int index, T item) { + if (index >= size || index < 0) + throw new ArrayIndexOutOfBoundsException(); + T old = array[index]; + array[index] = item; + return old; + } + + @Override + public void remove(int index) { + if (index >= size || index < 0) + throw new ArrayIndexOutOfBoundsException(); + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(array, index + 1, array, index, numMoved); + array[--size] = null; // let GC do its work + } + + @Override + public boolean remove(T item) { + if (item == null) { + for (int index = 0; index < size; index++) + if (array[index] == null) { + remove(index); + return true; + } + } else { + for (int index = 0; index < size; index++) + if (item.equals(array[index])) { + remove(index); + return true; + } + } + return false; + } + + @Override + public boolean contains(T item) { + return indexOf(item) >= 0; + } + + @Override + public int indexOf(T item) { + if (item == null) { + for (int i = 0; i < size; i++) + if (array[i]==null) + return i; + } else { + for (int i = 0; i < size; i++) + if (item.equals(array[i])) + return i; + } + return -1; + } + + + @Override + public void ensureCapacity(int newElementsCount) { + if (newElementsCount > array.length) { + int newCapacity = Math.max(newElementsCount, array.length * 2); + array = Arrays.copyOf(array, newCapacity); + } + } + + @Override + public int getCapacity() { + return array.length; + } + + @Override + public void reverse() { + for (int i = 0, mid = size >> 1, j = size - 1; i < mid; i++, j--) { + T temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + } + @Override + public String toString() { + return Arrays.toString(Arrays.copyOf(array, size)); + } + + @Override + public Object[] toArray() { + return Arrays.copyOf(array, size); + } + +} \ No newline at end of file diff --git a/module03/src/test/java/ru/sberbank/edu/CustomArrayImplTest.java b/module03/src/test/java/ru/sberbank/edu/CustomArrayImplTest.java new file mode 100644 index 00000000..cb061673 --- /dev/null +++ b/module03/src/test/java/ru/sberbank/edu/CustomArrayImplTest.java @@ -0,0 +1,195 @@ +package ru.sberbank.edu; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.*; + +class CustomArrayImplTest { + + + @Test + void addAllArray() { + CustomArrayImpl array = new CustomArrayImpl<>(); + Integer[] ints = {1,2,3,4}; + boolean addStatus = array.addAll(ints); + assertTrue(addStatus); + assertEquals(4, array.size()); + } + + @Test + void addAllCollection() { + CustomArrayImpl array = new CustomArrayImpl<>(); + Collection ints = Arrays.asList(1, 2, 3, 4); + boolean addStatus = array.addAll(ints); + assertTrue(addStatus); + assertEquals(4, array.size()); + } + + @Test + void addAllAtIndex() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(1); + array.add(2); + Integer[] newInts = {3, 4}; + assertTrue(array.addAll(1, newInts)); + assertEquals(4, array.size()); + assertEquals(3, array.get(1)); + } + + + + + @Test + void removeByIndex() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(1); + array.add(2); + array.add(3); + array.remove(1); + assertFalse(array.contains(2)); + } + + @Test + void removeByItem() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(1); + array.add(2); + array.add(3); + array.remove(Integer.valueOf(2)); + assertFalse(array.contains(2)); + } + + @Test + void contains() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(1); + array.add(2); + array.add(3); + assertTrue(array.contains(2)); + } + + @Test + void indexOf() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(1); + array.add(2); + array.add(5); + assertEquals(1, array.indexOf(2)); + } + + @Test + void reverse() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(1); + array.add(2); + array.add(5); + array.reverse(); + assertEquals(2, array.indexOf(1)); + } + @Test + void add() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(5); + assertTrue(array.contains(5)); + } + + @Test + void get() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(1); + array.add(2); + array.add(3); + assertEquals(2, array.get(1)); + } + + @Test + void size() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(1); + array.add(2); + array.add(3); + assertEquals(3, array.size()); + } + + @Test + void isEmpty() { + CustomArrayImpl array = new CustomArrayImpl<>(); + assertTrue(array.isEmpty()); + array.add(1); + assertFalse(array.isEmpty()); + } + + @Test + void set() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(1); + assertEquals(1, array.set(0, 2)); + assertEquals(2, array.get(0)); + assertThrows(ArrayIndexOutOfBoundsException.class, () -> array.set(5, 3)); + } + + @Test + void removeAt() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(1); + array.add(2); + assertDoesNotThrow(() -> array.remove(0)); + assertThrows(ArrayIndexOutOfBoundsException.class, () -> array.remove(5)); + } + + @Test + void remove() { + CustomArrayImpl array = new CustomArrayImpl<>(); + array.add(1); + array.add(2); + assertTrue(array.remove(new Integer(1))); + assertFalse(array.remove(new Integer(5))); + } + @Test + void ensureCapacity() { + CustomArrayImpl array = new CustomArrayImpl<>(); + + for(int i=0; i<10; i++){ + array.add(i); + } + + array.ensureCapacity(20); + assertEquals(20, array.getCapacity()); + } + + @Test + void getCapacity() { + CustomArrayImpl array = new CustomArrayImpl<>(); + + for(int i=0; i<10; i++){ + array.add(i); + } + + assertEquals(10, array.getCapacity()); + } + @Test + void toStringTest() { + CustomArrayImpl array = new CustomArrayImpl<>(); + for(int i=0; i<3; i++){ + array.add(i); + } + + String expected = "[0, 1, 2]"; + assertEquals(expected, array.toString()); + } + + @Test + void toArray() { + CustomArrayImpl array = new CustomArrayImpl<>(); + for(int i=0; i<3; i++){ + array.add(i); + } + + Object[] expected = new Object[] {0, 1, 2}; + assertArrayEquals(expected, array.toArray()); + } + +} \ No newline at end of file diff --git a/module04/src/main/java/ru/sberbank/edu/App.java b/module04/src/main/java/ru/sberbank/edu/App.java index 5419c026..dcdbd961 100644 --- a/module04/src/main/java/ru/sberbank/edu/App.java +++ b/module04/src/main/java/ru/sberbank/edu/App.java @@ -1,13 +1,23 @@ package ru.sberbank.edu; +import java.util.ArrayList; +import java.util.List; + /** * Hello world! * */ public class App { - public static void main( String[] args ) - { - System.out.println( "Hello World!" ); + public static void main( String[] args ) { + List base = List.of(3,2,5,8,10,0,3); + CustomDigitComparator cdc = new CustomDigitComparator(); + List listOfInt = new ArrayList<>(base); + listOfInt.sort(cdc); + System.out.println(listOfInt); + Person person1 = new Person("Ivan","Govnov", 20); + Person person2 = new Person("Ivan","Govnov", 20); + System.out.println(person2.hashCode()); + System.out.println(person1.hashCode()); } } diff --git a/module04/src/main/java/ru/sberbank/edu/CustomDigitComparator.java b/module04/src/main/java/ru/sberbank/edu/CustomDigitComparator.java new file mode 100644 index 00000000..118346b9 --- /dev/null +++ b/module04/src/main/java/ru/sberbank/edu/CustomDigitComparator.java @@ -0,0 +1,19 @@ +package ru.sberbank.edu; + +import java.util.Comparator; + +public class CustomDigitComparator implements Comparator { + @Override + public int compare(Integer lhs, Integer rhs) { + if (isEven(lhs) && !isEven(rhs)) { + return -1; + } + if (!isEven(lhs) && isEven(rhs)) { + return 1; + } else return 0; + } + + private boolean isEven(Integer i) { + return i % 2 == 0; + } +} \ No newline at end of file diff --git a/module04/src/main/java/ru/sberbank/edu/Person.java b/module04/src/main/java/ru/sberbank/edu/Person.java new file mode 100644 index 00000000..e328fea5 --- /dev/null +++ b/module04/src/main/java/ru/sberbank/edu/Person.java @@ -0,0 +1,63 @@ +package ru.sberbank.edu; + +public class Person implements Comparable{ + private String name; + private String city; + private Integer age; + + public Person(String name, String city, Integer age) { + this.name = name; + this.city = city; + this.age = age; + } + + @Override + public String toString() { + return "Person{" + + "name='" + name + '\'' + + ", city='" + city + '\'' + + ", age=" + age + + '}'; + } + + @Override + public boolean equals(Object o) { + if (o==null) return false; + if (!(o instanceof Person)) return false; + + Person person = (Person) o; + + if (!name.equalsIgnoreCase(person.name)) return false; + if (!city.equalsIgnoreCase(person.city)) return false; + return age.equals(person.age); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + city.hashCode(); + result = 31 * result + age.hashCode(); + return result; + } + + @Override + public int compareTo(Person otherPerson) { + int cityCompare = this.city.compareTo(otherPerson.city); + if(cityCompare!=0){ + return cityCompare; + } + return this.name.compareTo(otherPerson.name); + } + + public String getName() { + return name; + } + + public String getCity() { + return city; + } + + public Integer getAge() { + return age; + } +} \ No newline at end of file diff --git a/module04/src/test/java/ru/sberbank/edu/AppTest.java b/module04/src/test/java/ru/sberbank/edu/AppTest.java deleted file mode 100644 index 895d735c..00000000 --- a/module04/src/test/java/ru/sberbank/edu/AppTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package ru.sberbank.edu; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * Unit test for simple App. - */ -public class AppTest - extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); - } - - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } - - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } -} diff --git a/module04/src/test/java/ru/sberbank/edu/CustomDigitComparatorTest.java b/module04/src/test/java/ru/sberbank/edu/CustomDigitComparatorTest.java new file mode 100644 index 00000000..622dc56e --- /dev/null +++ b/module04/src/test/java/ru/sberbank/edu/CustomDigitComparatorTest.java @@ -0,0 +1,21 @@ +package ru.sberbank.edu; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CustomDigitComparatorTest { + @Test + void checkCompareTest(){ + CustomDigitComparator comparator = new CustomDigitComparator(); + int even = comparator.compare(2,3); + assertEquals(-1,even); + int notEven = comparator.compare(-5,2); + assertEquals(1,notEven); + int equal = comparator.compare(-2,2); + assertEquals(0,equal); + int zero = comparator.compare(0,0); + assertEquals(0,zero); + + } +} diff --git a/module04/src/test/java/ru/sberbank/edu/PersonTest.java b/module04/src/test/java/ru/sberbank/edu/PersonTest.java new file mode 100644 index 00000000..c4d40b09 --- /dev/null +++ b/module04/src/test/java/ru/sberbank/edu/PersonTest.java @@ -0,0 +1,70 @@ +package ru.sberbank.edu; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class PersonTest { + + @Test + void testCompareTo_SameCityAndSameName() { + Person person1 = new Person("John", "New York", 30); + Person person2 = new Person("John", "New York", 25); + + int result = person1.compareTo(person2); + + Assertions.assertEquals(0, result); + } + + @Test + void testCompareTo_SameCityAndDifferentName() { + Person person1 = new Person("Alice", "New York", 30); + Person person2 = new Person("Bob", "New York", 25); + + int result = person1.compareTo(person2); + + assertTrue(result < 0); + } + + @Test + void testCompareTo_DifferentCity() { + Person person1 = new Person("John", "Los Angeles", 30); + Person person2 = new Person("Bob", "New York", 25); + + int result = person1.compareTo(person2); + + assertTrue(result < 0); + } + + @Test + void testEquals_SamePerson() { + Person person1 = new Person("John", "New York", 30); + Person person2 = new Person("John", "New York", 30); + + assertTrue(person2.equals(person1)); + } + + @Test + void testEquals_DifferentPerson() { + Person person1 = new Person("John", "New York", 30); + Person person2 = new Person("Alice", "Los Angeles", 25); + + assertFalse(person2.equals(person1)); + } + + @Test + void checkHashCodeTest(){ + Person person1 = new Person("John", "New York", 30); + Person person2 = new Person("John", "New York", 30); + int person1HashCode = person1.hashCode(); + int person2HashCode = person2.hashCode(); + assertEquals(person1HashCode,person2HashCode); + } + @Test + void checkToStringTest(){ + Person person1 = new Person("John", "New York", 30); + String result = person1.toString(); + assertEquals("Person{name='John', city='New York', age=30}",result); + } +} \ No newline at end of file diff --git a/module05/README.md b/module05/README.md index 5ff44151..10add341 100644 --- a/module05/README.md +++ b/module05/README.md @@ -2,7 +2,8 @@ ## Задание №1 -1. Реализовать класс GeoPosition, который хранит координаты города (широта и долгота в радианах) Конструктор принимает координаты в градусах, которые затем преобразовываются в конструкторе в радианы +1. Реализовать класс GeoPosition, который хранит координаты города (широта и долгота в радианах) + Конструктор принимает координаты в градусах, которые затем преобразовываются в конструкторе в радианы Пример входных значений конструктора: - 55 - 55(45'07'') diff --git a/module05/src/main/java/ru/sberbank/edu/App.java b/module05/src/main/java/ru/sberbank/edu/App.java index 5419c026..e6150de4 100644 --- a/module05/src/main/java/ru/sberbank/edu/App.java +++ b/module05/src/main/java/ru/sberbank/edu/App.java @@ -6,8 +6,24 @@ */ public class App { - public static void main( String[] args ) - { - System.out.println( "Hello World!" ); + public static void main( String[] args ) { + GeoPosition city = new GeoPosition("55(01'01'')", "55(45'47'')"); + GeoPosition city1 = new GeoPosition("22(01'01'')", "33(45'47'')"); + GeoPosition city3 = new GeoPosition("55(01'03'')", "55(45'48'')"); + System.out.println(city.getLatitude()); + System.out.println(city.getLongitude()); + System.out.println(city.toString()); + CityInfo cityInfo = new CityInfo("Gorod", city); + CityInfo cityInfo1 = new CityInfo("City", city1); + CityInfo cityInfo3 = new CityInfo("assa", city3); + System.out.println(cityInfo.toString()); + TravelService travelService = new TravelService(); + travelService.add(cityInfo); + travelService.add(cityInfo1); + System.out.println(travelService.citiesNames()); + System.out.println(travelService.getDistance(cityInfo,cityInfo1)); + System.out.println(travelService.getCitiesNear(cityInfo3,10000)); + + } } diff --git a/module05/src/main/java/ru/sberbank/edu/CityInfo.java b/module05/src/main/java/ru/sberbank/edu/CityInfo.java index f37134e2..6214f6ac 100644 --- a/module05/src/main/java/ru/sberbank/edu/CityInfo.java +++ b/module05/src/main/java/ru/sberbank/edu/CityInfo.java @@ -1,21 +1,64 @@ package ru.sberbank.edu; -/** - * City info - */ +import java.util.Objects; + public class CityInfo { + public void setName(String name) { + this.name = name; + } + + public void setPosition(GeoPosition position) { + this.position = position; + } + private String name; private GeoPosition position; - /** - * Ctor. - * - * @param name - city name - * @param position - position - */ + public CityInfo(String name, GeoPosition position) { this.name = name; this.position = position; } + + public CityInfo() { + + } + + public String getName() { + return name; + } + + public GeoPosition getPosition() { + return position; + } + + @Override + public String toString() { + return "CityInfo{" + + "name='" + name + '\'' + + ", position=" + position + + '}'; + } + + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (!(o.getClass()==CityInfo.class)) { + return false; + } + CityInfo cityInfo = (CityInfo) o; + if(!(this.name.equals(cityInfo.name))) { + return false; + } + if (!(this.position.getLatitude()==cityInfo.position.getLatitude())) { + return false; + } + return this.position.getLongitude()==cityInfo.position.getLongitude() ; + } + + @Override + public int hashCode() { + return Objects.hash(name, position); + } } \ No newline at end of file diff --git a/module05/src/main/java/ru/sberbank/edu/GeoPosition.java b/module05/src/main/java/ru/sberbank/edu/GeoPosition.java index 2dc4e21b..bc972b0a 100644 --- a/module05/src/main/java/ru/sberbank/edu/GeoPosition.java +++ b/module05/src/main/java/ru/sberbank/edu/GeoPosition.java @@ -4,27 +4,43 @@ * Geo position. */ public class GeoPosition { + private final double latitude; // широта в радианах + private final double longitude; // долгота в радианах - /** - * Широта в радианах. - */ - private double latitude; - - /** - * Долгота в радианах. - */ - private double longitude; - - /** - * Ctor. - * - * @param latitudeGradus - latitude in gradus - * @param longitudeGradus - longitude in gradus - * Possible values: 55, 55(45'07''), 59(57'00'') - */ public GeoPosition(String latitudeGradus, String longitudeGradus) { - // parse and set latitude and longitude in radian + this.latitude = parseDegrees(latitudeGradus); + this.longitude = parseDegrees(longitudeGradus); } - // getters and toString + private double parseDegrees(String degrees) { + if (!degrees.contains("(")) { + return Math.toRadians(Double.parseDouble(degrees)); + } + double radian = Double.parseDouble(degrees.substring(0, 2)); + int minutesIndex = degrees.indexOf("(") + 1; + int secondsIndex = degrees.indexOf("'") + 1; + double minutes = Double.parseDouble(degrees.substring(minutesIndex, secondsIndex - 1)); + double seconds = Double.parseDouble(degrees.substring(secondsIndex, degrees.length() - 3)); + + // Перевод минут и секунд в доли градуса + double degreesOffset = radian + (minutes / 60) + (seconds / 3600); + return Math.toRadians(degreesOffset); + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } + + + @Override + public String toString() { + return "GeoPosition{" + + "latitude=" + latitude + + ", longitude=" + longitude + + '}'; + } } \ No newline at end of file diff --git a/module05/src/main/java/ru/sberbank/edu/TravelService.java b/module05/src/main/java/ru/sberbank/edu/TravelService.java index 830d188c..60940b22 100644 --- a/module05/src/main/java/ru/sberbank/edu/TravelService.java +++ b/module05/src/main/java/ru/sberbank/edu/TravelService.java @@ -3,11 +3,9 @@ import java.util.ArrayList; import java.util.List; -/** - * Travel Service. - */ -public class TravelService { +public class TravelService { + private static final double EARTH_RADIUS_KM = 6371.0; // do not change type private final List cities = new ArrayList<>(); @@ -18,24 +16,34 @@ public class TravelService { * @throws IllegalArgumentException if city already exists */ public void add(CityInfo cityInfo) { - // do something + if (cities.stream().anyMatch(city -> city.equals(cityInfo))) { + throw new IllegalArgumentException("This city already added"); + } + cities.add(cityInfo); } /** * remove city info. * - * @param cityName - city name + * @param cityInfo - city name * @throws IllegalArgumentException if city doesn't exist + *

+ * ИСПРАВИЛ НА CITYINFO входной параметр! Могут существовать несколько городов с одним названием. Paris например штук 6. */ - public void remove(String cityName) { - // do something + public void remove(CityInfo cityInfo) { + List cityToRemove = cities.stream().filter(city -> city.equals(cityInfo)).toList(); + if (cityToRemove.isEmpty()) { + throw new IllegalArgumentException(String.format("City %s not found", cityInfo.getName())); + } + cities.removeAll(cityToRemove); } /** * Get cities names. */ public List citiesNames() { - return null; + return cities.stream() + .map(CityInfo::getName).toList(); } /** @@ -46,8 +54,17 @@ public List citiesNames() { * @param destCityName - destination city * @throws IllegalArgumentException if source or destination city doesn't exist. */ - public int getDistance(String srcCityName, String destCityName) { - return 0; + public double getDistance(CityInfo srcCityName, CityInfo destCityName) { + + double lat1 = srcCityName.getPosition().getLatitude(); + double lon1 = srcCityName.getPosition().getLongitude(); + double lat2 = destCityName.getPosition().getLatitude(); + double lon2 = destCityName.getPosition().getLongitude(); + double dlon = lon2 - lon1; + double dlat = lat2 - lat1; + double a = Math.pow(Math.sin(dlat / 2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon / 2), 2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return EARTH_RADIUS_KM * c; } /** @@ -57,7 +74,20 @@ public int getDistance(String srcCityName, String destCityName) { * @param radius - radius in kilometers for search * @throws IllegalArgumentException if city with cityName city doesn't exist. */ - public List getCitiesNear(String cityName, int radius) { - return null; + public List getCitiesNear(CityInfo cityName, double radius) { + List targetCity = cities.stream() + .filter(city -> { + double distance = getDistance(city, cityName); + return distance < radius; + }) + .toList(); + + if (targetCity.isEmpty()) { + throw new IllegalArgumentException("Вокруг нет городов в радиусе " + radius); + + } else { + return targetCity; + } } + } diff --git a/module05/src/test/java/ru/sberbank/edu/AppTest.java b/module05/src/test/java/ru/sberbank/edu/AppTest.java deleted file mode 100644 index 895d735c..00000000 --- a/module05/src/test/java/ru/sberbank/edu/AppTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package ru.sberbank.edu; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * Unit test for simple App. - */ -public class AppTest - extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); - } - - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } - - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } -} diff --git a/module05/src/test/java/ru/sberbank/edu/CityInfoTest.java b/module05/src/test/java/ru/sberbank/edu/CityInfoTest.java new file mode 100644 index 00000000..9bd85cb3 --- /dev/null +++ b/module05/src/test/java/ru/sberbank/edu/CityInfoTest.java @@ -0,0 +1,57 @@ +package ru.sberbank.edu; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class CityInfoTest { + + @Test + public void testGetName() { + CityInfo cityInfo = new CityInfo("CityName", new GeoPosition("55(01'01'')", "55(45'47'')")); + + assertEquals("CityName", cityInfo.getName()); + } + + @Test + public void testGetPosition() { + CityInfo cityInfo = new CityInfo("CityName", new GeoPosition("55(01'01'')", "55(45'47'')")); + + assertEquals(0.960226824942358, cityInfo.getPosition().getLatitude()); + assertEquals(0.9732489204169602, cityInfo.getPosition().getLongitude()); + } + + @Test + public void testToString() { + CityInfo cityInfo = new CityInfo("CityName", new GeoPosition("55(01'01'')", "55(45'47'')")); + + String expected = "CityInfo{name='CityName', position=GeoPosition{latitude=0.960226824942358, longitude=0.9732489204169602}}"; + assertEquals(expected, cityInfo.toString()); + } + + @Test + public void testEquals() { + CityInfo cityInfo1 = new CityInfo("CityName", new GeoPosition("55(01'01'')", "55(45'47'')")); + CityInfo cityInfo2 = new CityInfo("CityName", new GeoPosition("55(01'01'')", "55(45'47'')")); + + assertEquals(cityInfo2, cityInfo1); + } + + @Test + public void testEqualsWithDifferentName() { + CityInfo cityInfo1 = new CityInfo("CityName", new GeoPosition("55(01'01'')", "55(45'47'')")); + CityInfo cityInfo2 = new CityInfo("CityName ", new GeoPosition("55(01'01'')", "55(45'47'')")); + + assertNotEquals(cityInfo2, cityInfo1); + } + + @Test + public void testEqualsWithDifferentLatitude() { + CityInfo cityInfo1 = new CityInfo("CityName", new GeoPosition("55(01'00'')", "55(45'47'')")); + CityInfo cityInfo2 = new CityInfo("CityName", new GeoPosition("55(01'01'')", "55(45'47'')")); + + assertNotEquals(cityInfo2, cityInfo1); + } + +} diff --git a/module05/src/test/java/ru/sberbank/edu/GeoPositionTest.java b/module05/src/test/java/ru/sberbank/edu/GeoPositionTest.java new file mode 100644 index 00000000..5d2b6972 --- /dev/null +++ b/module05/src/test/java/ru/sberbank/edu/GeoPositionTest.java @@ -0,0 +1,45 @@ +package ru.sberbank.edu; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GeoPositionTest { + + @Test + public void testGetLatitude() { + GeoPosition geoPosition = new GeoPosition("10.5", "20.5"); + + assertEquals(0.1832595714594046, geoPosition.getLatitude()); + } + + @Test + public void testGetLongitude() { + GeoPosition geoPosition = new GeoPosition("10.5", "20.5"); + + assertEquals(0.35779249665883756, geoPosition.getLongitude()); + } + + @Test + public void testToString() { + GeoPosition geoPosition = new GeoPosition("10.5", "20.5"); + + String expected = "GeoPosition{latitude=0.1832595714594046, longitude=0.35779249665883756}"; + assertEquals(expected, geoPosition.toString()); + } + + @Test + public void testGetLatitudeWithSecs() { + GeoPosition geoPosition = new GeoPosition("55(01'01'')", "55(45'47'')"); + + assertEquals(0.960226824942358, geoPosition.getLatitude()); + } + + @Test + public void testGetLongitudeWithSecs() { + GeoPosition geoPosition = new GeoPosition("55(01'01'')", "55(45'47'')"); + + assertEquals(0.9732489204169602, geoPosition.getLongitude()); + } + +} diff --git a/module05/src/test/java/ru/sberbank/edu/TravelServiceTest.java b/module05/src/test/java/ru/sberbank/edu/TravelServiceTest.java new file mode 100644 index 00000000..6bb3858f --- /dev/null +++ b/module05/src/test/java/ru/sberbank/edu/TravelServiceTest.java @@ -0,0 +1,99 @@ +package ru.sberbank.edu; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TravelServiceTest { + + TravelService travelService; + + @BeforeEach + public void setUp() { + travelService = new TravelService(); + + CityInfo moscow = new CityInfo(); + moscow.setName("Moscow"); + moscow.setPosition(new GeoPosition("55(01'01'')", "55(01'01'')")); + + CityInfo saintPetersburg = new CityInfo(); + saintPetersburg.setName("Saint Petersburg"); + saintPetersburg.setPosition(new GeoPosition("75(01'01'')", "75(01'01'')")); + + travelService.add(moscow); + travelService.add(saintPetersburg); + } + + @Test + public void addCity_AlreadyExists_ThrowsException() { + CityInfo city = new CityInfo(); + city.setName("Moscow"); + city.setPosition(new GeoPosition("55(01'01'')", "55(01'01'')")); + + Assertions.assertThrows(IllegalArgumentException.class, () -> travelService.add(city)); + } + + @Test + public void removeCity_NotFound_ThrowsException() { + CityInfo city = new CityInfo(); + city.setName("Berlin"); + + Assertions.assertThrows(IllegalArgumentException.class, () -> travelService.remove(city)); + } + + @Test + public void removeCity_Successful_NoException() { + CityInfo moscow = new CityInfo(); + moscow.setName("Moscow"); + moscow.setPosition(new GeoPosition("55(01'01'')", "55(01'01'')")); + Assertions.assertDoesNotThrow(() -> travelService.remove(moscow)); + } + + @Test + public void citiesNames_ReturnsCorrectCitiesList() { + List cityNames = travelService.citiesNames(); + + Assertions.assertTrue(cityNames.contains("Moscow")); + Assertions.assertTrue(cityNames.contains("Saint Petersburg")); + assertEquals(2, cityNames.size()); + } + + @Test + public void getDistance_BetweenTwoCities_ReturnsCorrectDistance() { + CityInfo moscow = new CityInfo(); + moscow.setName("Moscow"); + moscow.setPosition(new GeoPosition("55(01'01'')", "55(01'01'')")); + + CityInfo saintPetersburg = new CityInfo(); + saintPetersburg.setName("Saint Petersburg"); + saintPetersburg.setPosition(new GeoPosition("75(01'01'')", "75(01'01'')")); + + double distance = travelService.getDistance(moscow, saintPetersburg); + + assertEquals(2384.844535114297, distance); + } + + @Test + public void getCitiesNear_WithinRadius_ReturnsCorrectListOfCities() { + CityInfo city = new CityInfo(); + city.setName("city"); + city.setPosition(new GeoPosition("55(01'01'')", "55(01'01'')")); + + List nearbyCities = travelService.getCitiesNear(city, 100000); + + assertEquals(nearbyCities.size(), 2); + } + + @Test + public void getCitiesNear_NoCitiesWithinRadius_ThrowsException() { + CityInfo city = new CityInfo(); + city.setName("city"); + city.setPosition(new GeoPosition("99(01'01'')", "99(01'01'')")); + + Assertions.assertThrows(IllegalArgumentException.class, () -> travelService.getCitiesNear(city, 10.0)); + } +} \ No newline at end of file diff --git a/module06/src/main/java/ru/sberbank/edu/repository/CarDbRepositoryImpl.java b/module06/src/main/java/ru/sberbank/edu/repository/CarDbRepositoryImpl.java index 0a7a2d25..2cfefac4 100644 --- a/module06/src/main/java/ru/sberbank/edu/repository/CarDbRepositoryImpl.java +++ b/module06/src/main/java/ru/sberbank/edu/repository/CarDbRepositoryImpl.java @@ -3,27 +3,36 @@ import ru.sberbank.edu.model.Car; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; +import java.sql.*; +import java.util.Collection; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; public class CarDbRepositoryImpl implements CarRepository { private final Connection connection; - private static final String CREATE_CAR_SQL = "INSERT INTO car (id, model) VALUES (?,?)"; - private static final String UPDATE_CAR_SQL = "UPDATE car SET model = ? WHERE id = ?"; - private static final String SELECT_CAR_BY_ID = "SELECT * FROM car WHERE id = ?"; + public static final String CREATE_CAR_SQL = "INSERT INTO car (id, model) VALUES (?,?)"; + public static final String UPDATE_CAR_SQL = "UPDATE car SET model = ? WHERE id = ?"; + public static final String SELECT_CAR_BY_ID = "SELECT * FROM car WHERE id = ?"; + public static final String DELETE_CAR_BY_ID = "DELETE FROM car WHERE id = ?"; + public static final String DELETE_ALL_CARS = "DELETE FROM car"; + public static final String FIND_ALL_CARS = "SELECT * FROM car"; + public static final String SELECT_CAR_BY_MODEL = "SELECT * FROM car WHERE model = ?"; + public static final String COUNT_ROWS_BY_ID = "SELECT COUNT(*) FROM car where id = ?"; private final PreparedStatement createPreStmt; private final PreparedStatement updatePreStmt; private final PreparedStatement findByIdPreStmt; + private final PreparedStatement deleteByIdPreStmt; + private final PreparedStatement findByModelPreStmt; public CarDbRepositoryImpl(Connection connection) throws SQLException { this.connection = connection; this.createPreStmt = connection.prepareStatement(CREATE_CAR_SQL); this.updatePreStmt = connection.prepareStatement(UPDATE_CAR_SQL); this.findByIdPreStmt = connection.prepareStatement(SELECT_CAR_BY_ID); + this.deleteByIdPreStmt = connection.prepareStatement(DELETE_CAR_BY_ID); + this.findByModelPreStmt = connection.prepareStatement(SELECT_CAR_BY_MODEL); } @Override @@ -41,6 +50,37 @@ public Car createOrUpdate(Car car) throws SQLException { return car; } + @Override + public Set createAll(Collection cars) { + cars + .forEach(car->{ + try { + createPreStmt.setString(1,car.getId()); + createPreStmt.setString(2, car.getModel()); + createPreStmt.executeUpdate(); + } catch (SQLException throwables) { + throwables.printStackTrace(); + } + + }); + return new HashSet<>(cars); + } + + @Override + public Set findAll() { + Set cars = new HashSet<>(); + try(Statement statement = connection.createStatement(); + ResultSet resultSet= statement.executeQuery(FIND_ALL_CARS)){ + String id = resultSet.getString("id"); + String model = resultSet.getString("model"); + Car car = new Car(id,model); + cars.add(car); + } catch (SQLException throwables) { + throwables.printStackTrace(); + } + return cars; + } + @Override public Optional findById(String id) throws SQLException { // validation @@ -61,11 +101,27 @@ public Optional findById(String id) throws SQLException { @Override public Boolean deleteById(String id) { - return null; + try { + deleteByIdPreStmt.setString(1, id); + deleteByIdPreStmt.executeUpdate(); + return true; + } catch (SQLException e){ + return false; + } + } + + @Override + public Boolean deleteAll() { + try(Statement statement = connection.createStatement()){ + statement.executeUpdate(DELETE_ALL_CARS); + return true; + }catch (SQLException e){ + return false; + } } private int countRowsById(String id) throws SQLException { - PreparedStatement preparedStatement = connection.prepareStatement("SELECT COUNT(*) FROM car where id = ?"); + PreparedStatement preparedStatement = connection.prepareStatement(COUNT_ROWS_BY_ID); preparedStatement.setString(1, id); ResultSet resultSet = preparedStatement.executeQuery(); int rowCount = 0; @@ -74,4 +130,25 @@ private int countRowsById(String id) throws SQLException { } return rowCount; } + + @Override + public Set findByModel(String model) { + Set cars = new HashSet<>(); + try { + findByModelPreStmt.setString(1, model); + ResultSet resultSet = findByModelPreStmt.executeQuery(); + while (resultSet.next()) { + String id = resultSet.getString("id"); + Car car = new Car(id, model); + cars.add(car); + } + resultSet.close(); + } catch (SQLException e){ + throw new RuntimeException(e); + } + return cars; + } + + + } diff --git a/module06/src/main/java/ru/sberbank/edu/service/CarServiceImpl.java b/module06/src/main/java/ru/sberbank/edu/service/CarServiceImpl.java index e5eb91b4..d4f916c2 100644 --- a/module06/src/main/java/ru/sberbank/edu/service/CarServiceImpl.java +++ b/module06/src/main/java/ru/sberbank/edu/service/CarServiceImpl.java @@ -26,6 +26,11 @@ public void editModel(String id, String newModel) throws SQLException { updateCarModel(car, newModel); } + @Override + public void deleteCar(String id) { + carRepository.deleteById(id); + } + private void updateCarModel(Car car, String newModel) { car.setModel(newModel); try { diff --git a/module06/src/test/java/ru/sberbank/edu/AppTest.java b/module06/src/test/java/ru/sberbank/edu/AppTest.java deleted file mode 100644 index 895d735c..00000000 --- a/module06/src/test/java/ru/sberbank/edu/AppTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package ru.sberbank.edu; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * Unit test for simple App. - */ -public class AppTest - extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); - } - - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } - - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } -} diff --git a/module06/src/test/java/ru/sberbank/edu/CarDbRepositoryImplTest.java b/module06/src/test/java/ru/sberbank/edu/CarDbRepositoryImplTest.java new file mode 100644 index 00000000..1a991414 --- /dev/null +++ b/module06/src/test/java/ru/sberbank/edu/CarDbRepositoryImplTest.java @@ -0,0 +1,127 @@ +package ru.sberbank.edu; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import ru.sberbank.edu.model.Car; +import ru.sberbank.edu.repository.CarDbRepositoryImpl; + +import java.sql.*; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static ru.sberbank.edu.repository.CarDbRepositoryImpl.*; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class CarDbRepositoryImplTest { + + @Mock + private Connection connection; + + @Mock + private PreparedStatement createPreStmt; + @Mock + private PreparedStatement statement; + @Mock + private PreparedStatement deleteByIdPreStmt; + @Mock + private PreparedStatement findByModelPreStmt; + + @Mock + private ResultSet resultSet; + + private CarDbRepositoryImpl carDbRepositoryImpl; + + @BeforeEach + void setUp() throws SQLException { + when(connection.prepareStatement(CREATE_CAR_SQL)).thenReturn(createPreStmt); + when(connection.createStatement()).thenReturn(statement); + when(statement.executeQuery(anyString())).thenReturn(resultSet); + when(connection.prepareStatement(DELETE_CAR_BY_ID)).thenReturn(deleteByIdPreStmt); + when(connection.prepareStatement(SELECT_CAR_BY_MODEL)).thenReturn(findByModelPreStmt); + carDbRepositoryImpl = new CarDbRepositoryImpl(connection); + } + @Test + void testCreateAll() throws SQLException { + Car car = new Car("1", "ModelX"); + Set cars = Collections.singleton(car); + Set result = carDbRepositoryImpl.createAll(cars); + verify(createPreStmt, times(1)).executeUpdate(); + assertEquals(cars, result); + } + @Test + void testFindAll() throws SQLException { + // Arrange + Car car = new Car("1", "ModelX"); + when(resultSet.next()).thenReturn(true).thenReturn(false); + when(resultSet.getString("id")).thenReturn(car.getId()); + when(resultSet.getString("model")).thenReturn(car.getModel()); + Set expectedCars = Collections.singleton(car); + + + Set result = carDbRepositoryImpl.findAll(); + + + verify(statement, times(1)).executeQuery(CarDbRepositoryImpl.FIND_ALL_CARS); + assertEquals(expectedCars, result); + } + @Test + public void testDeleteByIdSuccess() throws SQLException { + String id = "someId"; + when(deleteByIdPreStmt.executeUpdate()).thenReturn(1); // + boolean result = carDbRepositoryImpl.deleteById(id); + assertTrue(result); + verify(deleteByIdPreStmt).setString(1, id); // + verify(deleteByIdPreStmt).executeUpdate(); + } + @Test + public void testDeleteAllSuccess() throws SQLException { + Statement statement = mock(Statement.class); + when(connection.createStatement()).thenReturn(statement); + when(statement.executeUpdate(DELETE_ALL_CARS)).thenReturn(1); + boolean result = carDbRepositoryImpl.deleteAll(); + assertTrue(result); + verify(statement).executeUpdate(anyString()); + } + @Test + public void whenFindByModel_thenReturnsSetOfCars() throws SQLException { + // Arrange + String model = "TestModel"; + when(findByModelPreStmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, true, false); // Возвращаем два автомобиля и затем завершаем итерацию + when(resultSet.getString("id")).thenReturn("1", "2"); + + // Act + Set result = carDbRepositoryImpl.findByModel(model); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.stream().anyMatch(car -> car.getId().equals("1"))); + assertTrue(result.stream().anyMatch(car -> car.getId().equals("2"))); + verify(findByModelPreStmt, times(1)).setString(1, model); // Проверяем, что setString вызывался один раз с корректными параметрами + verify(findByModelPreStmt, times(1)).executeQuery(); // Проверяем, что executeQuery вызывался ровно один раз + verify(resultSet, atLeastOnce()).close(); // Проверяем, что resultSet был закрыт + } + + @Test + public void whenFindByModelAndSQLException_thenThrowsRuntimeException() throws SQLException { + // Arrange + String model = "TestModel"; + when(findByModelPreStmt.executeQuery()).thenThrow(new SQLException()); + + // Act and Assert + assertThrows(RuntimeException.class, () -> carDbRepositoryImpl.findByModel(model)); + } + + +} \ No newline at end of file diff --git a/module07/src/main/java/ru/sberbank/edu/WeatherCache.java b/module07/src/main/java/ru/sberbank/edu/WeatherCache.java index e0a2d02b..cb5e4e01 100644 --- a/module07/src/main/java/ru/sberbank/edu/WeatherCache.java +++ b/module07/src/main/java/ru/sberbank/edu/WeatherCache.java @@ -1,40 +1,30 @@ package ru.sberbank.edu; +import java.time.LocalDateTime; import java.util.HashMap; -import java.util.Map; + public class WeatherCache { - private final Map cache = new HashMap<>(); + private static final int EXPIRY_DURATION_MINUTES = 5; private final WeatherProvider weatherProvider; + private final HashMap cache = new HashMap<>(); - /** - * Constructor. - * - * @param weatherProvider - weather provider - */ public WeatherCache(WeatherProvider weatherProvider) { this.weatherProvider = weatherProvider; } - /** - * Get ACTUAL weather info for current city or null if current city not found. - * If cache doesn't contain weather info OR contains NOT ACTUAL info then we should download info - * If you download weather info then you should set expiry time now() plus 5 minutes. - * If you can't download weather info then remove weather info for current city from cache. - * - * @param city - city - * @return actual weather info - */ - public WeatherInfo getWeatherInfo(String city) { - // should be implemented - return null; + public synchronized WeatherInfo getWeatherInfo(String city) { + WeatherInfo info = cache.get(city); + if (info == null || info.getExpiryTime().isBefore(LocalDateTime.now())) { + info = weatherProvider.get(city); + info.setExpiryTime(LocalDateTime.now().plusMinutes(EXPIRY_DURATION_MINUTES)); + cache.put(city, info); + } + return info; } - /** - * Remove weather info from cache. - **/ - public void removeWeatherInfo(String city) { - // should be implemented + public synchronized void removeWeatherInfo(String city) { + cache.remove(city); } } diff --git a/module07/src/main/java/ru/sberbank/edu/WeatherInfo.java b/module07/src/main/java/ru/sberbank/edu/WeatherInfo.java index 9fcee430..651257d6 100644 --- a/module07/src/main/java/ru/sberbank/edu/WeatherInfo.java +++ b/module07/src/main/java/ru/sberbank/edu/WeatherInfo.java @@ -5,42 +5,75 @@ public class WeatherInfo { private String city; - - /** - * Short weather description - * Like 'sunny', 'clouds', 'raining', etc - */ private String shortDescription; - - /** - * Weather description. - * Like 'broken clouds', 'heavy raining', etc - */ private String description; - - /** - * Temperature. - */ private double temperature; - - /** - * Temperature that fells like. - */ private double feelsLikeTemperature; - - /** - * Wind speed. - */ private double windSpeed; - - /** - * Pressure. - */ private double pressure; - - /** - * Expiry time of weather info. - * If current time is above expiry time then current weather info is not actual! - */ private LocalDateTime expiryTime; + + public String getCity() { + return city; + } + + public String getShortDescription() { + return shortDescription; + } + + public String getDescription() { + return description; + } + + public double getTemperature() { + return temperature; + } + + public double getFeelsLikeTemperature() { + return feelsLikeTemperature; + } + + public double getWindSpeed() { + return windSpeed; + } + + public double getPressure() { + return pressure; + } + + public LocalDateTime getExpiryTime() { + return expiryTime; + } + + public void setCity(String city) { + this.city = city; + } + + public void setShortDescription(String shortDescription) { + this.shortDescription = shortDescription; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setTemperature(double temperature) { + this.temperature = temperature; + } + + public void setFeelsLikeTemperature(double feelsLikeTemperature) { + this.feelsLikeTemperature = feelsLikeTemperature; + } + + public void setWindSpeed(double windSpeed) { + this.windSpeed = windSpeed; + } + + public void setPressure(double pressure) { + this.pressure = pressure; + } + + public void setExpiryTime(LocalDateTime expiryTime) { + this.expiryTime = expiryTime; + } } diff --git a/module07/src/main/java/ru/sberbank/edu/WeatherProvider.java b/module07/src/main/java/ru/sberbank/edu/WeatherProvider.java index 5fc6d989..947e48c9 100644 --- a/module07/src/main/java/ru/sberbank/edu/WeatherProvider.java +++ b/module07/src/main/java/ru/sberbank/edu/WeatherProvider.java @@ -1,18 +1,14 @@ package ru.sberbank.edu; -public class WeatherProvider { - // private RestTemplate restTemplate = null; +import org.springframework.web.client.RestTemplate; + +public class WeatherProvider { + private final RestTemplate restTemplate = new RestTemplate(); + private static final String API_URL = "http://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY}"; + private static final String API_KEY = "your_api_key_here"; - /** - * Download ACTUAL weather info from internet. - * You should call GET http://api.openweathermap.org/data/2.5/weather?q={city name}&appid={API key} - * You should use Spring Rest Template for calling requests - * - * @param city - city - * @return weather info or null - */ public WeatherInfo get(String city) { - return null; + return restTemplate.getForObject(API_URL, WeatherInfo.class, city, API_KEY); } } diff --git a/module07/src/test/java/ru/sberbank/edu/AppTest.java b/module07/src/test/java/ru/sberbank/edu/AppTest.java index 895d735c..10c2a471 100644 --- a/module07/src/test/java/ru/sberbank/edu/AppTest.java +++ b/module07/src/test/java/ru/sberbank/edu/AppTest.java @@ -1,38 +1,10 @@ package ru.sberbank.edu; -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; + /** * Unit test for simple App. */ -public class AppTest - extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); - } - - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } +public class AppTest { - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } } diff --git a/module08/pom.xml b/module08/pom.xml index 5ab321fb..c6f08af2 100644 --- a/module08/pom.xml +++ b/module08/pom.xml @@ -24,5 +24,10 @@ 3.8.1 test + + org.mortbay.jetty + servlet-api + 2.5-20081211 + diff --git a/module08/src/main/java/ru/sberbank/edu/FinancialServlet.java b/module08/src/main/java/ru/sberbank/edu/FinancialServlet.java index f012968f..f3d68c09 100644 --- a/module08/src/main/java/ru/sberbank/edu/FinancialServlet.java +++ b/module08/src/main/java/ru/sberbank/edu/FinancialServlet.java @@ -1,13 +1,53 @@ package ru.sberbank.edu; -/** - * Hello world! - * - */ -public class FinancialServlet +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + + +public class FinancialServlet extends HttpServlet { public static void main( String[] args ) { System.out.println( "Hello World!" ); } + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.println(""); + out.println("

Расчет доходности вклада

"); + out.println("
"); + out.println("Сумма на момент открытия:
"); + out.println("Годовая процентная ставка:
"); + out.println("Количество лет:
"); + out.println(""); + out.println("
"); + out.println(""); + } + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + try { + double sum = Double.parseDouble(request.getParameter("sum")); + double percentage = Double.parseDouble(request.getParameter("percentage")); + int years = Integer.parseInt(request.getParameter("years")); + + double finalAmount = sum * Math.pow(1 + (percentage / 100), years); + out.println(""); + out.println("

Результат расчета доходности вклада

"); + out.println("

Итоговая сумма после " + years + " лет: " + finalAmount + "

"); + out.println(""); + } catch (NumberFormatException e) { + out.println(""); + out.println("

Ошибка ввода данных

"); + out.println("

Пожалуйста, введите корректные данные.

"); + out.println(""); + } finally { + out.close(); + } + } } diff --git a/module08/src/main/webapp/WEB-INF/web.xml b/module08/src/main/webapp/WEB-INF/web.xml index f70f3c67..dbc41f39 100644 --- a/module08/src/main/webapp/WEB-INF/web.xml +++ b/module08/src/main/webapp/WEB-INF/web.xml @@ -1,7 +1,15 @@ - + - - Archetype Created Web Application - + + FinancialServlet + ru.sberbank.edu.FinancialServlet + + + + FinancialServlet + /finance + + + \ No newline at end of file diff --git a/module08/src/main/webapp/author.html b/module08/src/main/webapp/author.html new file mode 100644 index 00000000..35480ab5 --- /dev/null +++ b/module08/src/main/webapp/author.html @@ -0,0 +1,22 @@ + + + + + Автор работы + + + +
+

Информация об авторе

+
+ +
+

Имя: Канарский Александр Викторович

+

Контактная информация: alkanarskiy@gmail.com

+
+ + + + \ No newline at end of file diff --git a/module08/src/main/webapp/index.html b/module08/src/main/webapp/index.html new file mode 100644 index 00000000..37ffc3c7 --- /dev/null +++ b/module08/src/main/webapp/index.html @@ -0,0 +1,40 @@ + + + + + Car market - ГАЗ-24 + + + +
+

Car market

+
+ +

Автомобиль ГАЗ-24

+Газ-24 + +

Технические характеристики

+
    +
  • Размеры: длина х ширина х высота
  • +
  • Колея: передняя/задняя
  • +
  • Ширина передней/задней колеи
  • +
  • Размер колес
  • +
+ +

Варианты исполнения

+
    +
  1. Седан
  2. +
  3. Универсал
  4. +
  5. Пикап
  6. +
+ +
+

Автор работы

+ Канарский Александр Викторович +
+ +
+ +
+ + \ No newline at end of file diff --git a/module08/src/main/webapp/style.css b/module08/src/main/webapp/style.css new file mode 100644 index 00000000..d89bea53 --- /dev/null +++ b/module08/src/main/webapp/style.css @@ -0,0 +1,33 @@ +body { + font-family: Arial, sans-serif; +} + +header, .author-section, footer { + margin: 20px; + padding: 10px; +} + +header p, .author-section h2, footer button, footer a { + text-align: center; +} + +img { + display: block; + margin: 0 auto; + width: 300px; + height: auto; +} + +.author-info p { + text-align: left; + margin-left: 20px; +} + +button { + display: block; + margin: 0 auto; +} + +footer { + margin-top: 20px; +} \ No newline at end of file diff --git a/module09/pom.xml b/module09/pom.xml index 59059ad5..1417f541 100644 --- a/module09/pom.xml +++ b/module09/pom.xml @@ -15,5 +15,24 @@ UTF-8 + + + org.springframework + spring-context + 3.0.2.RELEASE + compile + + + org.springframework + spring-web + 3.0.2.RELEASE + compile + + + cglib + cglib-nodep + 3.0 + + diff --git a/module09/src/main/java/ru/sberbank/edu/App.java b/module09/src/main/java/ru/sberbank/edu/App.java index 5419c026..3d39a220 100644 --- a/module09/src/main/java/ru/sberbank/edu/App.java +++ b/module09/src/main/java/ru/sberbank/edu/App.java @@ -1,13 +1,20 @@ package ru.sberbank.edu; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + /** * Hello world! * */ -public class App +public class App { - public static void main( String[] args ) - { - System.out.println( "Hello World!" ); + public static void main(String[] args) { + ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + + WeatherCache cache = context.getBean(WeatherCache.class); + + WeatherInfo weatherInfo = cache.getWeatherInfo("OMSK"); + System.out.println("GOOD! weather=" + weatherInfo); } } diff --git a/module09/src/main/java/ru/sberbank/edu/AppConfig.java b/module09/src/main/java/ru/sberbank/edu/AppConfig.java new file mode 100644 index 00000000..494d1a22 --- /dev/null +++ b/module09/src/main/java/ru/sberbank/edu/AppConfig.java @@ -0,0 +1,15 @@ +package ru.sberbank.edu; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class AppConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + +} \ No newline at end of file diff --git a/module09/src/main/java/ru/sberbank/edu/WeatherCache.java b/module09/src/main/java/ru/sberbank/edu/WeatherCache.java index 3f236f72..7102f17e 100644 --- a/module09/src/main/java/ru/sberbank/edu/WeatherCache.java +++ b/module09/src/main/java/ru/sberbank/edu/WeatherCache.java @@ -1,40 +1,35 @@ package ru.sberbank.edu; +import org.springframework.beans.factory.annotation.Autowired; + +import java.time.LocalDateTime; import java.util.HashMap; -import java.util.Map; + /** * Weather cache. */ public class WeatherCache { - private final Map cache = new HashMap<>(); - private WeatherProvider weatherProvider; - - /** - * Default constructor. - */ - public WeatherCache() { + private static final int EXPIRY_DURATION_MINUTES = 5; + private final WeatherProvider weatherProvider; + private final HashMap cache = new HashMap<>(); + @Autowired + public WeatherCache(WeatherProvider weatherProvider) { + this.weatherProvider = weatherProvider; } - /** - * Get ACTUAL weather info for current city or null if current city not found. - * If cache doesn't contain weather info OR contains NOT ACTUAL info then we should download info - * If you download weather info then you should set expiry time now() plus 5 minutes. - * If you can't download weather info then remove weather info for current city from cache. - * - * @param city - city - * @return actual weather info - */ - public WeatherInfo getWeatherInfo(String city) { - // should be implemented - return null; + public synchronized WeatherInfo getWeatherInfo(String city) { + WeatherInfo info = cache.get(city); + if (info == null || info.getExpiryTime().isBefore(LocalDateTime.now())) { + info = weatherProvider.get(city); + info.setExpiryTime(LocalDateTime.now().plusMinutes(EXPIRY_DURATION_MINUTES)); + cache.put(city, info); + } + return info; } - /** - * Remove weather info from cache. - **/ - public void removeWeatherInfo(String city) { - // should be implemented + public synchronized void removeWeatherInfo(String city) { + cache.remove(city); } } \ No newline at end of file diff --git a/module09/src/main/java/ru/sberbank/edu/WeatherInfo.java b/module09/src/main/java/ru/sberbank/edu/WeatherInfo.java index 06d81ffc..4f72caa1 100644 --- a/module09/src/main/java/ru/sberbank/edu/WeatherInfo.java +++ b/module09/src/main/java/ru/sberbank/edu/WeatherInfo.java @@ -8,42 +8,75 @@ public class WeatherInfo { private String city; - - /** - * Short weather description - * Like 'sunny', 'clouds', 'raining', etc - */ private String shortDescription; - - /** - * Weather description. - * Like 'broken clouds', 'heavy raining', etc - */ private String description; - - /** - * Temperature. - */ private double temperature; - - /** - * Temperature that fells like. - */ private double feelsLikeTemperature; - - /** - * Wind speed. - */ private double windSpeed; - - /** - * Pressure. - */ private double pressure; - - /** - * Expiry time of weather info. - * If current time is above expiry time then current weather info is not actual! - */ private LocalDateTime expiryTime; + + public String getCity() { + return city; + } + + public String getShortDescription() { + return shortDescription; + } + + public String getDescription() { + return description; + } + + public double getTemperature() { + return temperature; + } + + public double getFeelsLikeTemperature() { + return feelsLikeTemperature; + } + + public double getWindSpeed() { + return windSpeed; + } + + public double getPressure() { + return pressure; + } + + public LocalDateTime getExpiryTime() { + return expiryTime; + } + + public void setCity(String city) { + this.city = city; + } + + public void setShortDescription(String shortDescription) { + this.shortDescription = shortDescription; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setTemperature(double temperature) { + this.temperature = temperature; + } + + public void setFeelsLikeTemperature(double feelsLikeTemperature) { + this.feelsLikeTemperature = feelsLikeTemperature; + } + + public void setWindSpeed(double windSpeed) { + this.windSpeed = windSpeed; + } + + public void setPressure(double pressure) { + this.pressure = pressure; + } + + public void setExpiryTime(LocalDateTime expiryTime) { + this.expiryTime = expiryTime; + } } diff --git a/module09/src/main/java/ru/sberbank/edu/WeatherProvider.java b/module09/src/main/java/ru/sberbank/edu/WeatherProvider.java index 02a2fb68..50132f42 100644 --- a/module09/src/main/java/ru/sberbank/edu/WeatherProvider.java +++ b/module09/src/main/java/ru/sberbank/edu/WeatherProvider.java @@ -3,20 +3,27 @@ /** * Weather provider */ +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +@Service public class WeatherProvider { + private final RestTemplate restTemplate; + + @Value("${app.apiurl}") + private String apiUrl; + + @Value("${app.apikey}") + private String apiKey; + + @Autowired + public WeatherProvider(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } -// private RestTemplate restTemplate; -// private String appKey; - /** - * Download ACTUAL weather info from internet. - * You should call GET http://api.openweathermap.org/data/2.5/weather?q={city name}&appid={API key} - * You should use Spring Rest Template for calling requests - * - * @param city - city - * @return weather info or null - */ public WeatherInfo get(String city) { - return null; + return restTemplate.getForObject(apiUrl, WeatherInfo.class, city, apiKey); } } diff --git a/module09/src/main/resources/app.properties b/module09/src/main/resources/app.properties new file mode 100644 index 00000000..a13e24ce --- /dev/null +++ b/module09/src/main/resources/app.properties @@ -0,0 +1,2 @@ +app.apiurl="http://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY}" +app.apikey="43bb1e9d7e177b6ff0681feb9647b8d4" \ No newline at end of file diff --git a/module10/pom.xml b/module10/pom.xml index 96f5c1b2..e80c6b06 100644 --- a/module10/pom.xml +++ b/module10/pom.xml @@ -4,6 +4,7 @@ homework-javareboot-2023-group-06 ru.sberbank.edu 1.0-SNAPSHOT + 4.0.0 @@ -16,4 +17,26 @@ UTF-8
+ + + + + org.springframework.boot + spring-boot-starter-web + 3.2.0 + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + 3.2.0 + + + org.hibernate + hibernate-validator + 8.0.0.Final + + + diff --git a/module10/src/main/java/ru/sberbank/edu/App.java b/module10/src/main/java/ru/sberbank/edu/App.java index 5419c026..3d7a3824 100644 --- a/module10/src/main/java/ru/sberbank/edu/App.java +++ b/module10/src/main/java/ru/sberbank/edu/App.java @@ -1,13 +1,13 @@ package ru.sberbank.edu; -/** - * Hello world! - * - */ +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication public class App { - public static void main( String[] args ) - { - System.out.println( "Hello World!" ); + public static void main(String[] args) { + SpringApplication.run(App.class, args); } } diff --git a/module10/src/main/java/ru/sberbank/edu/FinanceController.java b/module10/src/main/java/ru/sberbank/edu/FinanceController.java new file mode 100644 index 00000000..d57bdffd --- /dev/null +++ b/module10/src/main/java/ru/sberbank/edu/FinanceController.java @@ -0,0 +1,40 @@ +package ru.sberbank.edu; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/finance") +public class FinanceController { + @Value("${finance.minimum-deposit}") + private double minimumDeposit; + + @GetMapping + public String showFinanceForm(Model model) { + FinanceForm financeForm = new FinanceForm(); + model.addAttribute("financeForm", financeForm); + return "financeForm"; + } + + @PostMapping + public String calculateInterest(@Validated @ModelAttribute("financeForm") FinanceForm form, BindingResult result, Model model) { + if (result.hasErrors() || form.getSum() < minimumDeposit) { + if (form.getSum() < minimumDeposit) { + model.addAttribute("error", "Минимальная сумма на момент открытия вклада " + minimumDeposit + " рублей"); + } else { + model.addAttribute("error", "Неверный формат данных. Скорректируйте значения"); + } + return "financeForm"; + } + double amount = form.getSum() * Math.pow(1 + form.getPercentage() / 100, form.getYears()); + model.addAttribute("amount", amount); + return "financeResult"; + } +} \ No newline at end of file diff --git a/module10/src/main/java/ru/sberbank/edu/FinanceForm.java b/module10/src/main/java/ru/sberbank/edu/FinanceForm.java new file mode 100644 index 00000000..749ed0f9 --- /dev/null +++ b/module10/src/main/java/ru/sberbank/edu/FinanceForm.java @@ -0,0 +1,41 @@ +package ru.sberbank.edu; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +public class FinanceForm { + @NotNull + @Min(0) + private Double sum; + + @NotNull + @Min(0) + private Double percentage; + + @NotNull + @Min(0) + private Integer years; + + public Double getSum() { + return sum; + } + + public void setSum(Double sum) { + this.sum = sum; + } + + public Double getPercentage() { + return percentage; + } + + public void setPercentage(Double percentage) { + this.percentage = percentage; + } + + public Integer getYears() { + return years; + } + + public void setYears(Integer years) { + this.years = years; + } +} diff --git a/module10/src/main/resources/Templates/financeForm.html b/module10/src/main/resources/Templates/financeForm.html new file mode 100644 index 00000000..e4d392f6 --- /dev/null +++ b/module10/src/main/resources/Templates/financeForm.html @@ -0,0 +1,27 @@ + + + + Калькулятор доходности вклада + + +

Калькулятор доходности вклада

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ + diff --git a/module10/src/main/resources/Templates/financeResult.html b/module10/src/main/resources/Templates/financeResult.html new file mode 100644 index 00000000..6ddfbd00 --- /dev/null +++ b/module10/src/main/resources/Templates/financeResult.html @@ -0,0 +1,10 @@ + + + Результат расчета + + +

Результат расчета

+

+Рассчитать ещё раз + + \ No newline at end of file diff --git a/module10/src/main/resources/application.yaml b/module10/src/main/resources/application.yaml new file mode 100644 index 00000000..2bbf30bb --- /dev/null +++ b/module10/src/main/resources/application.yaml @@ -0,0 +1,4 @@ +server: + port: 8080 +finance: + minimum-deposit: 50000 \ No newline at end of file diff --git a/module10/src/test/java/ru/sberbank/edu/AppTest.java b/module10/src/test/java/ru/sberbank/edu/AppTest.java deleted file mode 100644 index 895d735c..00000000 --- a/module10/src/test/java/ru/sberbank/edu/AppTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package ru.sberbank.edu; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * Unit test for simple App. - */ -public class AppTest - extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); - } - - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } - - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } -} diff --git a/module11/src/main/java/com/example/module11/Application.java b/module11/src/main/java/com/example/module11/Application.java index f6de7930..473ffad5 100644 --- a/module11/src/main/java/com/example/module11/Application.java +++ b/module11/src/main/java/com/example/module11/Application.java @@ -7,6 +7,7 @@ public class Application { public static void main(String[] args) { + SpringApplication.run(Application.class, args); } diff --git a/module11/src/main/java/com/example/module11/controller/UserController.java b/module11/src/main/java/com/example/module11/controller/UserController.java new file mode 100644 index 00000000..fbfe87db --- /dev/null +++ b/module11/src/main/java/com/example/module11/controller/UserController.java @@ -0,0 +1,53 @@ +package com.example.module11.controller; + +import com.example.module11.entity.MyUser; +import com.example.module11.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/users") +public class UserController { + private UserRepository userRepository; + + public UserController(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @GetMapping + public List getUsers() { + return userRepository.findAll(); + } + + + @PostMapping + public MyUser createUser(@RequestBody MyUser user) { + return userRepository.save(user); + } + @PutMapping("/{id}") + public MyUser updateUser(@PathVariable Long id, @RequestBody MyUser myUserDetails) { + MyUser myUser = userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found with id " + id)); + + myUser.setName(myUserDetails.getName()); + myUser.setAge(myUserDetails.getAge()); + return userRepository.save(myUser); + } + @DeleteMapping("/{id}") + public Map deleteUser(@PathVariable Long id) { + MyUser myUser = userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found with id " + id)); + + userRepository.delete(myUser); + Map response = new HashMap<>(); + response.put("deleted", Boolean.TRUE); + return response; + } + @GetMapping("/{id}") + public MyUser getUserById(@PathVariable Long id) { + return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found with id " + id)); + } + +} diff --git a/module11/src/main/java/com/example/module11/entity/MyUser.java b/module11/src/main/java/com/example/module11/entity/MyUser.java new file mode 100644 index 00000000..fca3cb40 --- /dev/null +++ b/module11/src/main/java/com/example/module11/entity/MyUser.java @@ -0,0 +1,50 @@ +package com.example.module11.entity; + + +import jakarta.persistence.*; + +@Entity +public class MyUser { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + private Integer age; + + // Конструкторы, геттеры и сеттеры + public MyUser() { + } + + public MyUser(Long id, String name, Integer age) { + this.id = id; + this.name = name; + this.age = age; + } + + // Getter и Setter методы + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } +} \ No newline at end of file diff --git a/module11/src/main/java/com/example/module11/entity/User.java b/module11/src/main/java/com/example/module11/entity/User.java deleted file mode 100644 index 49c12ffd..00000000 --- a/module11/src/main/java/com/example/module11/entity/User.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.module11.entity; - -public class User { - - private Long id; - private String name; - private Integer age; -} diff --git a/module11/src/main/java/com/example/module11/repository/UserRepository.java b/module11/src/main/java/com/example/module11/repository/UserRepository.java new file mode 100644 index 00000000..030c5a2a --- /dev/null +++ b/module11/src/main/java/com/example/module11/repository/UserRepository.java @@ -0,0 +1,10 @@ +package com.example.module11.repository; + +import com.example.module11.entity.MyUser; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { + +} \ No newline at end of file diff --git a/module11/src/main/resources/application.properties b/module11/src/main/resources/application.properties deleted file mode 100644 index 8b137891..00000000 --- a/module11/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/module11/src/main/resources/application.yaml b/module11/src/main/resources/application.yaml new file mode 100644 index 00000000..9cd575a1 --- /dev/null +++ b/module11/src/main/resources/application.yaml @@ -0,0 +1,26 @@ +logging: + level: + root: INFO + +server: + port: 8080 + address: localhost + +spring: + datasource: + url: jdbc:h2:mem:testdb + driverClassName: org.h2.Driver + username: sa + password: + jpa: + hibernate: + ddl-auto: create-drop + properties: + hibernate: + show_sql: true + format_sql: true + + h2: + console: + enabled: true + path: /h2-console diff --git a/module11/src/test/java/com/example/module11/Module11ApplicationTests.java b/module11/src/test/java/com/example/module11/Module11ApplicationTests.java deleted file mode 100644 index 0ec40701..00000000 --- a/module11/src/test/java/com/example/module11/Module11ApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.module11; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class Module11ApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/module11/src/test/java/com/example/module11/UserControllerTest.java b/module11/src/test/java/com/example/module11/UserControllerTest.java new file mode 100644 index 00000000..40ab1992 --- /dev/null +++ b/module11/src/test/java/com/example/module11/UserControllerTest.java @@ -0,0 +1,113 @@ +package com.example.module11; + +import com.example.module11.entity.MyUser; +import com.example.module11.repository.UserRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.example.module11.controller.UserController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doNothing; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; + +@ExtendWith(MockitoExtension.class) +public class UserControllerTest { + + @Mock + private UserRepository userRepository; + + @InjectMocks + private UserController userController; + + private MockMvc mockMvc; + + @BeforeEach + public void setup() { + mockMvc = standaloneSetup(userController).build(); + } + + @Test + public void testGetUsers() throws Exception { + List users = Arrays.asList(new MyUser(1L, "Alice", 30), new MyUser(2L, "Bob", 25)); + given(userRepository.findAll()).willReturn(users); + + mockMvc.perform(get("/users")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name").value("Alice")) + .andExpect(jsonPath("$[1].name").value("Bob")); + } + + @Test + public void testCreateUser() throws Exception { + MyUser newUser = new MyUser(null, "Charlie", 35); + MyUser savedUser = new MyUser(3L, "Charlie", 35); + given(userRepository.save(any(MyUser.class))).willReturn(savedUser); + + ObjectMapper objectMapper = new ObjectMapper(); + String userJson = objectMapper.writeValueAsString(newUser); + + mockMvc.perform(post("/users") + .contentType(MediaType.APPLICATION_JSON) + .content(userJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Charlie")) + .andExpect(jsonPath("$.age").value(35)); + } + + @Test + public void testUpdateUser() throws Exception { + Long userId = 1L; + MyUser existingUser = new MyUser(userId, "Alice", 30); + MyUser updatedUser = new MyUser(userId, "Alice Updated", 35); + given(userRepository.findById(userId)).willReturn(Optional.of(existingUser)); + given(userRepository.save(existingUser)).willReturn(updatedUser); + + ObjectMapper objectMapper = new ObjectMapper(); + String userJson = objectMapper.writeValueAsString(updatedUser); + + mockMvc.perform(put("/users/{id}", userId) + .contentType(MediaType.APPLICATION_JSON) + .content(userJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Alice Updated")) + .andExpect(jsonPath("$.age").value(35)); + } + + @Test + public void testDeleteUser() throws Exception { + Long userId = 1L; + MyUser existingUser = new MyUser(userId, "Alice", 30); + given(userRepository.findById(userId)).willReturn(Optional.of(existingUser)); + doNothing().when(userRepository).delete(existingUser); + + mockMvc.perform(delete("/users/{id}", userId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.deleted").value(true)); + } + + @Test + public void testGetUserById() throws Exception { + Long userId = 1L; + MyUser user = new MyUser(userId, "Alice", 30); + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + + mockMvc.perform(get("/users/{id}", userId)) + .andExpect(status().isOk()).andExpect(jsonPath("$.name").value("Alice")) + .andExpect(jsonPath("$.age").value(30)); + } +} \ No newline at end of file diff --git a/module12/pom.xml b/module12/pom.xml index df62b2cb..0e0a8585 100644 --- a/module12/pom.xml +++ b/module12/pom.xml @@ -25,6 +25,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-security + com.h2database @@ -36,6 +40,17 @@ spring-boot-starter-test test + + org.springframework.security + spring-security-test + test + + + org.projectlombok + lombok + RELEASE + compile + diff --git a/module12/src/main/java/ru/edu/module12/Application.java b/module12/src/main/java/ru/edu/module12/Application.java index 45fd2fdb..3091f15e 100644 --- a/module12/src/main/java/ru/edu/module12/Application.java +++ b/module12/src/main/java/ru/edu/module12/Application.java @@ -2,6 +2,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @SpringBootApplication public class Application { diff --git a/module12/src/main/java/ru/edu/module12/config/SecurityConfig.java b/module12/src/main/java/ru/edu/module12/config/SecurityConfig.java new file mode 100644 index 00000000..d07c0a25 --- /dev/null +++ b/module12/src/main/java/ru/edu/module12/config/SecurityConfig.java @@ -0,0 +1,59 @@ +package ru.edu.module12.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import ru.edu.module12.entity.Auth; +import ru.edu.module12.repository.AuthRepository; + +import java.util.stream.Collectors; +@Configuration +@EnableWebSecurity +public class SecurityConfig { + @Autowired + private AuthRepository authRepository; + + @Bean + public UserDetailsService userDetailsService() { + return username -> { + Auth auth = authRepository.findByUsername(username); + if (auth == null) { + throw new UsernameNotFoundException("Пользователь " + username + " не найден."); + } + return new org.springframework.security.core.userdetails.User( + auth.getUsername(), + auth.getPassword(), + auth.getRoles().stream() + .map(role -> new SimpleGrantedAuthority(role.getName())) + .collect(Collectors.toList())); + }; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf((csrf) -> csrf.disable()) + .authorizeHttpRequests((authorize) -> authorize + .requestMatchers(HttpMethod.GET).hasAuthority("ROLE_USER") + .requestMatchers(HttpMethod.POST).hasAuthority("ROLE_ADMIN") + .anyRequest().authenticated()) + .httpBasic(Customizer.withDefaults()); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/module12/src/main/java/ru/edu/module12/controller/UsersRestController.java b/module12/src/main/java/ru/edu/module12/controller/UsersRestController.java new file mode 100644 index 00000000..eeff6768 --- /dev/null +++ b/module12/src/main/java/ru/edu/module12/controller/UsersRestController.java @@ -0,0 +1,49 @@ +package ru.edu.module12.controller; + +import org.springframework.web.bind.annotation.*; +import ru.edu.module12.entity.MyUser; +import ru.edu.module12.repository.MyUserRepository; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/users") +public class UsersRestController { + private MyUserRepository myUserRepository; + public UsersRestController(MyUserRepository myUserRepository) { + this.myUserRepository = myUserRepository; + } + + @GetMapping + public List getUsers() { + return myUserRepository.findAll(); + } + @PostMapping + public MyUser createUser(@RequestBody MyUser user) { + return myUserRepository.save(user); + } + @PutMapping("/{id}") + public MyUser updateUser(@PathVariable Long id, @RequestBody MyUser myUserDetails) { + MyUser myUser = myUserRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found with id " + id)); + + myUser.setName(myUserDetails.getName()); + myUser.setAge(myUserDetails.getAge()); + return myUserRepository.save(myUser); + } + @DeleteMapping("/{id}") + public Map deleteUser(@PathVariable Long id) { + MyUser myUser = myUserRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found with id " + id)); + + myUserRepository.delete(myUser); + Map response = new HashMap<>(); + response.put("deleted", Boolean.TRUE); + return response; + } + @GetMapping("/{id}") + public MyUser getUserById(@PathVariable Long id) { + return myUserRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found with id " + id)); + } + +} diff --git a/module12/src/main/java/ru/edu/module12/entity/Auth.java b/module12/src/main/java/ru/edu/module12/entity/Auth.java new file mode 100644 index 00000000..a19eb0bd --- /dev/null +++ b/module12/src/main/java/ru/edu/module12/entity/Auth.java @@ -0,0 +1,36 @@ +package ru.edu.module12.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.HashSet; +import java.util.Set; + +@Entity +@Table(name = "auth") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class Auth { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String username; + private String password; + + @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinTable( + name = "auth_roles", + joinColumns = @JoinColumn(name = "auth_id"), + inverseJoinColumns = @JoinColumn(name = "role_id") + ) + private Set roles = new HashSet<>(); + + +} diff --git a/module12/src/main/java/ru/edu/module12/entity/MyUser.java b/module12/src/main/java/ru/edu/module12/entity/MyUser.java new file mode 100644 index 00000000..fe334b2e --- /dev/null +++ b/module12/src/main/java/ru/edu/module12/entity/MyUser.java @@ -0,0 +1,22 @@ +package ru.edu.module12.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class MyUser { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + private Integer age; +} \ No newline at end of file diff --git a/module12/src/main/java/ru/edu/module12/entity/Role.java b/module12/src/main/java/ru/edu/module12/entity/Role.java new file mode 100644 index 00000000..a1e67a60 --- /dev/null +++ b/module12/src/main/java/ru/edu/module12/entity/Role.java @@ -0,0 +1,24 @@ +package ru.edu.module12.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table(name = "roles") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class Role { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + +} diff --git a/module12/src/main/java/ru/edu/module12/entity/User.java b/module12/src/main/java/ru/edu/module12/entity/User.java deleted file mode 100644 index 15345bc8..00000000 --- a/module12/src/main/java/ru/edu/module12/entity/User.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.edu.module12.entity; - -public class User { - - private Long id; - private String name; - private Integer age; -} diff --git a/module12/src/main/java/ru/edu/module12/repository/AuthRepository.java b/module12/src/main/java/ru/edu/module12/repository/AuthRepository.java new file mode 100644 index 00000000..55e25242 --- /dev/null +++ b/module12/src/main/java/ru/edu/module12/repository/AuthRepository.java @@ -0,0 +1,10 @@ +package ru.edu.module12.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.edu.module12.entity.Auth; + +@Repository +public interface AuthRepository extends JpaRepository { + Auth findByUsername(String username); +} \ No newline at end of file diff --git a/module12/src/main/java/ru/edu/module12/repository/MyUserRepository.java b/module12/src/main/java/ru/edu/module12/repository/MyUserRepository.java new file mode 100644 index 00000000..b95234c0 --- /dev/null +++ b/module12/src/main/java/ru/edu/module12/repository/MyUserRepository.java @@ -0,0 +1,10 @@ +package ru.edu.module12.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.edu.module12.entity.MyUser; + +@Repository +public interface MyUserRepository extends JpaRepository { + +} diff --git a/module12/src/main/java/ru/edu/module12/repository/RoleRepository.java b/module12/src/main/java/ru/edu/module12/repository/RoleRepository.java new file mode 100644 index 00000000..65b2be0f --- /dev/null +++ b/module12/src/main/java/ru/edu/module12/repository/RoleRepository.java @@ -0,0 +1,9 @@ +package ru.edu.module12.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.edu.module12.entity.Role; +@Repository +public interface RoleRepository extends JpaRepository { + Role findByName(String name); +} diff --git a/module12/src/main/resources/application.yaml b/module12/src/main/resources/application.yaml new file mode 100644 index 00000000..b628c0e1 --- /dev/null +++ b/module12/src/main/resources/application.yaml @@ -0,0 +1,30 @@ +logging: + level: + root: WARN + +server: + port: 8080 + address: localhost + +spring: + datasource: + url: jdbc:h2:mem:testdb + driverClassName: org.h2.Driver + username: sa + password: + jpa: + defer-datasource-initialization: true + hibernate: + ddl-auto: create-drop + properties: + hibernate: + show_sql: true + format_sql: true + + h2: + console: + enabled: true + path: /h2-console + sql: + init: + mode: always diff --git a/module12/src/main/resources/data.sql b/module12/src/main/resources/data.sql new file mode 100644 index 00000000..28d9cf7a --- /dev/null +++ b/module12/src/main/resources/data.sql @@ -0,0 +1,7 @@ + +INSERT INTO roles (name) VALUES ('ROLE_ADMIN'); +INSERT INTO roles (name) VALUES ('ROLE_USER'); +INSERT INTO auth (username, password) VALUES ('admin', '$2a$10$rnxHUAo/gTfmUm2Rmf7lfeyIwMKcVOiU9cdnGWIrFDXivSGOsCvde'); +INSERT INTO auth_roles (auth_id, role_id) SELECT (SELECT id FROM auth WHERE username = 'admin'), (SELECT id FROM roles WHERE name = 'ROLE_ADMIN'); +INSERT INTO auth (username, password) VALUES ('user', '$2a$10$rnxHUAo/gTfmUm2Rmf7lfeyIwMKcVOiU9cdnGWIrFDXivSGOsCvde'); +INSERT INTO auth_roles (auth_id, role_id) SELECT (SELECT id FROM auth WHERE username = 'user'), (SELECT id FROM roles WHERE name = 'ROLE_USER'); \ No newline at end of file diff --git a/module12/src/test/java/SecurityConfigTest.java b/module12/src/test/java/SecurityConfigTest.java new file mode 100644 index 00000000..f4f1fcd5 --- /dev/null +++ b/module12/src/test/java/SecurityConfigTest.java @@ -0,0 +1,77 @@ +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import ru.edu.module12.Application; +import ru.edu.module12.controller.UsersRestController; +import ru.edu.module12.repository.MyUserRepository; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; + + +@SpringBootTest(classes = Application.class) +@AutoConfigureMockMvc +@WithMockUser +public class SecurityConfigTest { + private MockMvc mvc; + + @Autowired + private WebApplicationContext wac; + + @BeforeEach + public void setup() { + this.mvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + } + + @WithMockUser(username = "admin", roles = "ADMIN") + @Test + public void shouldAllowAccessToUserRole() throws Exception { + // Поставка POST-запроса с CSRF и пользователем с ролью ADMIN + mvc.perform(get("/users") + .with(csrf())) // CSRF disabled globally, but we can still use it in tests) + .andExpect(status().isOk()); + } + + @WithMockUser(username = "admin", roles = "ADMIN") + @Test + public void shouldAllowAccessToAdminRole() throws Exception { + // Поставка POST-запроса с CSRF и пользователем с ролью ADMIN + mvc.perform(post("/users") + .with(csrf()) // CSRF disabled globally, but we can still use it in tests + .contentType("application/json") + .content("{\"id\": 2, \"name\": \"Petr\", \"age\": \"12\"}")) + .andExpect(status().isOk()); + } + + @WithMockUser(username = "user", roles = "USER") + @Test + public void shouldDenyAccessToNonAdminRole() throws Exception { + // Пользователь с ролью USER не должен иметь доступ к POST /users + mvc.perform(post("/users") + .with(csrf()) + .contentType("application/json") + .content("{\"id\": 2, \"name\": \"Petr\", \"age\": \"12\"}")) + .andExpect(status().isForbidden()); + } + + @Test + public void shouldDenyAccessToUnauthenticatedUsers() throws Exception { + // Неаутентифицированные пользователи не должны иметь доступ к POST /users + mvc.perform(post("/users") + .contentType("application/json") + .content("{\"id\": 2, \"name\": \"Petr\", \"age\": \"12\"}")) + .andExpect(status().isUnauthorized()); + } +} \ No newline at end of file diff --git a/module12/src/test/java/UsersRestControllerTest.java b/module12/src/test/java/UsersRestControllerTest.java new file mode 100644 index 00000000..88aab569 --- /dev/null +++ b/module12/src/test/java/UsersRestControllerTest.java @@ -0,0 +1,113 @@ +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.edu.module12.controller.UsersRestController; +import ru.edu.module12.entity.MyUser; +import ru.edu.module12.repository.MyUserRepository; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; + +@ExtendWith(MockitoExtension.class) +@SpringBootConfiguration +public class UsersRestControllerTest { + + @Mock + private MyUserRepository myUserRepository; + + @InjectMocks + private UsersRestController usersRestController; + + private MockMvc mockMvc; + + @BeforeEach + public void setup() { + mockMvc = standaloneSetup(usersRestController).build(); + } + + @Test + public void testGetUsers() throws Exception { + List users = Arrays.asList(new MyUser(1L, "Alice", 30), new MyUser(2L, "Bob", 25)); + given(myUserRepository.findAll()).willReturn(users); + + mockMvc.perform(get("/users")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name").value("Alice")) + .andExpect(jsonPath("$[1].name").value("Bob")); + } + + @Test + public void testCreateUser() throws Exception { + MyUser newUser = new MyUser(null, "Charlie", 35); + MyUser savedUser = new MyUser(3L, "Charlie", 35); + given(myUserRepository.save(any(MyUser.class))).willReturn(savedUser); + + ObjectMapper objectMapper = new ObjectMapper(); + String userJson = objectMapper.writeValueAsString(newUser); + + mockMvc.perform(post("/users") + .contentType(MediaType.APPLICATION_JSON) + .content(userJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Charlie")) + .andExpect(jsonPath("$.age").value(35)); + } + + @Test + public void testUpdateUser() throws Exception { + Long userId = 1L; + MyUser existingUser = new MyUser(userId, "Alice", 30); + MyUser updatedUser = new MyUser(userId, "Alice Updated", 35); + given(myUserRepository.findById(userId)).willReturn(Optional.of(existingUser)); + given(myUserRepository.save(existingUser)).willReturn(updatedUser); + + ObjectMapper objectMapper = new ObjectMapper(); + String userJson = objectMapper.writeValueAsString(updatedUser); + + mockMvc.perform(put("/users/{id}", userId) + .contentType(MediaType.APPLICATION_JSON) + .content(userJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Alice Updated")) + .andExpect(jsonPath("$.age").value(35)); + } + + @Test + public void testDeleteUser() throws Exception { + Long userId = 1L; + MyUser existingUser = new MyUser(userId, "Alice", 30); + given(myUserRepository.findById(userId)).willReturn(Optional.of(existingUser)); + doNothing().when(myUserRepository).delete(existingUser); + + mockMvc.perform(delete("/users/{id}", userId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.deleted").value(true)); + } + + @Test + public void testGetUserById() throws Exception { + Long userId = 1L; + MyUser user = new MyUser(userId, "Alice", 30); + given(myUserRepository.findById(userId)).willReturn(Optional.of(user)); + + mockMvc.perform(get("/users/{id}", userId)) + .andExpect(status().isOk()).andExpect(jsonPath("$.name").value("Alice")) + .andExpect(jsonPath("$.age").value(30)); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2a2b5785..0b768ddf 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 ru.sberbank.edu homework-javareboot-2023-group-06 @@ -20,52 +20,50 @@ pom - homework-javareboot-2023-group-06 + homework-javareboot-2023-group-06 - - UTF-8 - 17 - 17 - 5.9.2 - 5.5.0 - 5.3.1 - 3.24.2 - + + UTF-8 + 17 + 17 + - - - org.junit.jupiter - junit-jupiter-engine - ${junit.version} - test - - - org.mockito - mockito-core - ${mockito.version} - test - - - org.mockito - mockito-junit-jupiter - ${mockito.junit.version} - test - - - org.assertj - assertj-core - ${assertj.version} - - + + + org.junit.jupiter + junit-jupiter-engine + 5.10.0 + test + + + + org.mockito + mockito-core + 5.6.0 + test + + + org.mockito + mockito-junit-jupiter + 5.3.1 + test + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + + - - - - - maven-surefire-plugin - 3.1.2 - - - -