diff --git a/ukelonn.backend/src/main/java/no/priv/bang/ukelonn/backend/UkelonnServiceProvider.java b/ukelonn.backend/src/main/java/no/priv/bang/ukelonn/backend/UkelonnServiceProvider.java index 8ea1e6ce..9d7b9521 100644 --- a/ukelonn.backend/src/main/java/no/priv/bang/ukelonn/backend/UkelonnServiceProvider.java +++ b/ukelonn.backend/src/main/java/no/priv/bang/ukelonn/backend/UkelonnServiceProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 Steinar Bang + * Copyright 2016-2020 Steinar Bang * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -41,6 +42,7 @@ import no.priv.bang.ukelonn.UkelonnException; import no.priv.bang.ukelonn.UkelonnService; import no.priv.bang.ukelonn.beans.Account; +import no.priv.bang.ukelonn.beans.Bonus; import no.priv.bang.ukelonn.beans.Notification; import no.priv.bang.ukelonn.beans.PasswordsWithUser; import no.priv.bang.ukelonn.beans.PerformedTransaction; @@ -146,7 +148,7 @@ public Account getAccount(String username) { public Account registerPerformedJob(PerformedTransaction job) { int accountId = job.getAccount().getAccountId(); int jobtypeId = job.getTransactionTypeId(); - double jobamount = job.getTransactionAmount(); + double jobamount = addBonus(job.getTransactionAmount()); Date timeofjob = job.getTransactionDate(); try(Connection connection = datasource.getConnection()) { try(PreparedStatement statement = connection.prepareStatement("insert into transactions (account_id, transaction_type_id,transaction_amount, transaction_time) values (?, ?, ?, ?)")) { @@ -460,10 +462,131 @@ public void notificationTo(String username, Notification notification) { notifications.add(notification); } + @Override + public List getActiveBonuses() { + List activebonuses = new ArrayList<>(); + try(Connection connection = datasource.getConnection()) { + try(PreparedStatement statement = connection.prepareStatement("select * from bonuses where enabled and start_date <= ? and end_date >= ?")) { + Timestamp today = Timestamp.from(new Date().toInstant()); + statement.setTimestamp(1, today); + statement.setTimestamp(2, today); + try (ResultSet results = statement.executeQuery()) { + while (results.next()) { + buildBonusFromResultSetAndAddToList(activebonuses, results); + } + } + } + } catch (SQLException e) { + logWarning("Failed to get list of active bonuses", e); + } + + return activebonuses; + } + + @Override + public List getAllBonuses() { + List allbonuses = new ArrayList<>(); + try(Connection connection = datasource.getConnection()) { + try(PreparedStatement statement = connection.prepareStatement("select * from bonuses")) { + try (ResultSet results = statement.executeQuery()) { + while (results.next()) { + buildBonusFromResultSetAndAddToList(allbonuses, results); + } + } + } + } catch (SQLException e) { + logWarning("Failed to get list of all bonuses", e); + } + + return allbonuses; + } + + @Override + public List createBonus(Bonus newBonus) { + String title = newBonus.getTitle(); + try(Connection connection = datasource.getConnection()) { + try(PreparedStatement statement = connection.prepareStatement("insert into bonuses (enabled, iconurl, title, description, bonus_factor, start_date, end_date) values (?, ?, ?, ?, ?, ?, ?)")) { + statement.setBoolean(1, newBonus.isEnabled()); + statement.setString(2, newBonus.getIconurl()); + statement.setString(3, title); + statement.setString(4, newBonus.getDescription()); + statement.setDouble(5, newBonus.getBonusFactor()); + Date startDate = newBonus.getStartDate() != null ? newBonus.getStartDate() : new Date(); + statement.setTimestamp(6, Timestamp.from(startDate.toInstant())); + Date endDate = newBonus.getEndDate() != null ? newBonus.getEndDate() : new Date(); + statement.setTimestamp(7, Timestamp.from(endDate.toInstant())); + statement.executeUpdate(); + } + } catch (SQLException e) { + logWarning(String.format("Failed to add Bonus with title \"%s\"", title), e); + } + + return getAllBonuses(); + } + + @Override + public List modifyBonus(Bonus updatedBonus) { + int id = updatedBonus.getBonusId(); + try(Connection connection = datasource.getConnection()) { + try(PreparedStatement statement = connection.prepareStatement("update bonuses set enabled=?, iconurl=?, title=?, description=?, bonus_factor=?, start_date=?, end_date=? where bonus_id=?")) { + statement.setBoolean(1, updatedBonus.isEnabled()); + statement.setString(2, updatedBonus.getIconurl()); + statement.setString(3, updatedBonus.getTitle()); + statement.setString(4, updatedBonus.getDescription()); + statement.setDouble(5, updatedBonus.getBonusFactor()); + statement.setTimestamp(6, Timestamp.from(updatedBonus.getStartDate().toInstant())); + statement.setTimestamp(7, Timestamp.from(updatedBonus.getEndDate().toInstant())); + statement.setInt(8, id); + statement.executeUpdate(); + } + } catch (SQLException e) { + logWarning(String.format("Failed to update Bonus with database id %d", id), e); + } + + return getAllBonuses(); + } + + @Override + public List deleteBonus(Bonus removedBonus) { + int id = removedBonus.getBonusId(); + try(Connection connection = datasource.getConnection()) { + try(PreparedStatement statement = connection.prepareStatement("delete from bonuses where bonus_id=?")) { + statement.setInt(1, id); + statement.executeUpdate(); + } + } catch (SQLException e) { + logWarning(String.format("Failed to delete Bonus with database id %d", id), e); + } + + return getAllBonuses(); + } + private ConcurrentLinkedQueue getNotificationQueueForUser(String username) { return notificationQueues.computeIfAbsent(username, k-> new ConcurrentLinkedQueue<>()); } + double addBonus(double transactionAmount) { + List activebonuses = getActiveBonuses(); + if (activebonuses.isEmpty()) { + return transactionAmount; + } + + double bonus = activebonuses.stream().mapToDouble(b -> b.getBonusFactor() * transactionAmount - transactionAmount).sum(); + return transactionAmount + bonus; + } + + void buildBonusFromResultSetAndAddToList(List allbonuses, ResultSet results) throws SQLException { + int id = results.getInt("bonus_id"); + boolean enabled = results.getBoolean("enabled"); + String iconurl = results.getString("iconurl"); + String title = results.getString("title"); + String description = results.getString("description"); + double bonusFactor = results.getDouble("bonus_factor"); + Date startDate = Date.from(results.getTimestamp("start_date").toInstant()); + Date endDate = Date.from(results.getTimestamp("end_date").toInstant()); + allbonuses.add(new Bonus(id, enabled, iconurl, title, description, bonusFactor, startDate, endDate)); + } + static boolean passwordsEqualsAndNotEmpty(PasswordsWithUser passwords) { if (passwords.getPassword() == null || passwords.getPassword().isEmpty()) { return false; diff --git a/ukelonn.backend/src/test/java/no/priv/bang/ukelonn/backend/UkelonnServiceBaseTest.java b/ukelonn.backend/src/test/java/no/priv/bang/ukelonn/backend/UkelonnServiceBaseTest.java index 8237fe74..5d1013c8 100644 --- a/ukelonn.backend/src/test/java/no/priv/bang/ukelonn/backend/UkelonnServiceBaseTest.java +++ b/ukelonn.backend/src/test/java/no/priv/bang/ukelonn/backend/UkelonnServiceBaseTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Steinar Bang + * Copyright 2016-2020 Steinar Bang * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import no.priv.bang.ukelonn.UkelonnService; import no.priv.bang.ukelonn.beans.Account; +import no.priv.bang.ukelonn.beans.Bonus; import no.priv.bang.ukelonn.beans.Notification; import no.priv.bang.ukelonn.beans.PerformedTransaction; import no.priv.bang.ukelonn.beans.SumYear; @@ -134,6 +135,36 @@ public List earningsSumOverMonth(String username) { // TODO Auto-generated method stub return null; } + + @Override + public List getActiveBonuses() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getAllBonuses() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List createBonus(Bonus newBonus) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List modifyBonus(Bonus updatedBonus) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List deleteBonus(Bonus removedBonus) { + // TODO Auto-generated method stub + return null; + } }; assertEquals("Hello world!", ukelonn.getMessage()); diff --git a/ukelonn.backend/src/test/java/no/priv/bang/ukelonn/backend/UkelonnServiceProviderTest.java b/ukelonn.backend/src/test/java/no/priv/bang/ukelonn/backend/UkelonnServiceProviderTest.java index c240149e..bc7c4d65 100644 --- a/ukelonn.backend/src/test/java/no/priv/bang/ukelonn/backend/UkelonnServiceProviderTest.java +++ b/ukelonn.backend/src/test/java/no/priv/bang/ukelonn/backend/UkelonnServiceProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Steinar Bang + * Copyright 2018-2020 Steinar Bang * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,8 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -45,6 +47,7 @@ import no.priv.bang.ukelonn.UkelonnException; import no.priv.bang.ukelonn.UkelonnService; import no.priv.bang.ukelonn.beans.Account; +import no.priv.bang.ukelonn.beans.Bonus; import no.priv.bang.ukelonn.beans.Notification; import no.priv.bang.ukelonn.beans.PasswordsWithUser; import no.priv.bang.ukelonn.beans.PerformedTransaction; @@ -1121,4 +1124,174 @@ public void testEarningsSumOverMonthWhenSqlExceptionIsThrown() throws Exception assertThat(logservice.getLogmessages().get(0)).startsWith("[WARNING] Failed to get sum of earnings per month for account"); } + @Test + public void testGetCreateModifyAndDeleteBonuses() { + UkelonnServiceProvider ukelonn = getUkelonnServiceSingleton(); + int initialBonusCount = ukelonn.getAllBonuses().size(); + + // Verify that without any bonuses addBonus() will + // return the job registration transaction amount unchanged + double amount = 25.0; + assertEquals(amount, ukelonn.addBonus(amount), 0.0); + + // Add an enabled bonus with start date before today and end date after today + // this will show up as an active bonus + Date julestart = Date.from(LocalDateTime.now().minusDays(3).toInstant(ZoneOffset.UTC)); + Date juleslutt = Date.from(LocalDateTime.now().plusDays(3).toInstant(ZoneOffset.UTC)); + Bonus julebonus = new Bonus(0, true, null, "Julebonus", "Dobbelt betaling for utførte jobber", 2.0, julestart, juleslutt); + Bonus enabledBonus = ukelonn.createBonus(julebonus).stream().filter(b -> "Julebonus".equals(b.getTitle())).findFirst().get(); + int bonusCountWithOneAddedBonus = ukelonn.getAllBonuses().size(); + assertThat(bonusCountWithOneAddedBonus).isGreaterThan(initialBonusCount); + + // Verify that the active bonus will double the payment + // of registered jobs. + double expectAmount = 2 * amount; + assertEquals(expectAmount, ukelonn.addBonus(amount), 0.0); + + // Add an extra active bonus to verify that two + // concurrent bonuses will give the expected result + Bonus julebonus2 = ukelonn.createBonus(new Bonus(0, true, null, "Julebonuz", "Dobbelt betaling for utførte jobber", 1.25, julestart, juleslutt)).stream().filter(b -> "Julebonuz".equals(b.getTitle())).findFirst().get(); + double expectAmount2 = julebonus.getBonusFactor() * amount + julebonus2.getBonusFactor() * amount - amount; + assertEquals(expectAmount2, ukelonn.addBonus(amount), 0.0); + ukelonn.deleteBonus(julebonus2); + + // Add an inactive bonus with start and end date both in the future + // Since we're outside of the startDate/endDate, this will not show up + // as an active bonus + Date paaskestart = Date.from(LocalDateTime.now().plusDays(5).toInstant(ZoneOffset.UTC)); + Date paaskeslutt = Date.from(LocalDateTime.now().plusDays(10).toInstant(ZoneOffset.UTC)); + Bonus paaskebonus = new Bonus(0, true, null, "Påskebonus", "Dobbelt betaling for utførte jobber", 2.0, paaskestart, paaskeslutt); + Bonus inactiveBonus = ukelonn.createBonus(paaskebonus).stream().filter(b -> "Påskebonus".equals(b.getTitle())).findFirst().get(); + assertThat(ukelonn.getAllBonuses().size()).isGreaterThan(bonusCountWithOneAddedBonus); + + // Verify that active count is larger than 0 and is less than total count + List activeBonuses = ukelonn.getActiveBonuses(); + assertThat(activeBonuses).isNotEmpty(); + int activeBonusCount = activeBonuses.size(); + assertThat(ukelonn.getAllBonuses().size()).isGreaterThan(activeBonusCount); + + // Verify that active count is greater than initial count + assertThat(activeBonusCount).isGreaterThan(initialBonusCount); + + // Change the enabled bonus to set the enabled flag to false, and keep the rest of the values + // (ie. deactivate the currenly active bonus) + List bonuses = ukelonn.modifyBonus(disableBonus(enabledBonus)); + Bonus disabledBonus = bonuses.stream().filter(b -> b.getBonusId() == enabledBonus.getBonusId()).findFirst().get(); + assertFalse(disabledBonus.isEnabled()); + assertEquals(enabledBonus.getTitle(), disabledBonus.getTitle()); + assertEquals(enabledBonus.getDescription(), disabledBonus.getDescription()); + assertEquals(enabledBonus.getBonusFactor(), disabledBonus.getBonusFactor(), 0.0); + assertEquals(enabledBonus.getStartDate(), disabledBonus.getStartDate()); + assertEquals(enabledBonus.getEndDate(), disabledBonus.getEndDate()); + + // Verify that the active bonus count is less than before the update + assertThat(ukelonn.getActiveBonuses().size()).isLessThan(activeBonusCount); + + // Delete both bonuses and verify that the count decreases + int countBeforeDelete = bonuses.size(); + bonuses = ukelonn.deleteBonus(disabledBonus); + int countAfterFirstDelete = bonuses.size(); + assertThat(countAfterFirstDelete).isLessThan(countBeforeDelete); + bonuses = ukelonn.deleteBonus(inactiveBonus); + assertEquals(initialBonusCount, bonuses.size()); + } + + @SuppressWarnings("unchecked") + @Test + public void testGetActiveBonusesWithSQLException() throws Exception { + UkelonnServiceProvider ukelonn = new UkelonnServiceProvider(); + DataSource datasource = mock(DataSource.class); + when(datasource.getConnection()).thenThrow(SQLException.class); + MockLogService logservice = new MockLogService(); + ukelonn.setLogservice(logservice); + ukelonn.setDataSource(datasource); + ukelonn.activate(); + + // Verify that what we get with an SQL failure + // is an empty result and a warning in the log + List bonuses = ukelonn.getActiveBonuses(); + assertThat(bonuses).isEmpty(); + assertThat(logservice.getLogmessages()).isNotEmpty(); + assertThat(logservice.getLogmessages().get(0)).startsWith("[WARNING] Failed to get list of active bonuses"); + } + + @SuppressWarnings("unchecked") + @Test + public void testGetAllBonusesWithSQLException() throws Exception { + UkelonnServiceProvider ukelonn = new UkelonnServiceProvider(); + DataSource datasource = mock(DataSource.class); + when(datasource.getConnection()).thenThrow(SQLException.class); + MockLogService logservice = new MockLogService(); + ukelonn.setLogservice(logservice); + ukelonn.setDataSource(datasource); + ukelonn.activate(); + + // Verify that what we get with an SQL failure + // is an empty result and a warning in the log + List bonuses = ukelonn.getAllBonuses(); + assertThat(bonuses).isEmpty(); + assertThat(logservice.getLogmessages()).isNotEmpty(); + assertThat(logservice.getLogmessages().get(0)).startsWith("[WARNING] Failed to get list of all bonuses"); + } + + @SuppressWarnings("unchecked") + @Test + public void testAddBonusWithSQLException() throws Exception { + UkelonnServiceProvider ukelonn = new UkelonnServiceProvider(); + DataSource datasource = mock(DataSource.class); + when(datasource.getConnection()).thenThrow(SQLException.class); + MockLogService logservice = new MockLogService(); + ukelonn.setLogservice(logservice); + ukelonn.setDataSource(datasource); + ukelonn.activate(); + + // Verify that what we get with an SQL failure + // is an empty result and a warning in the log + List bonuses = ukelonn.createBonus(new Bonus()); + assertThat(bonuses).isEmpty(); + assertThat(logservice.getLogmessages()).isNotEmpty(); + assertThat(logservice.getLogmessages().get(0)).startsWith("[WARNING] Failed to add Bonus"); + } + + @SuppressWarnings("unchecked") + @Test + public void testUpdateBonusWithSQLException() throws Exception { + UkelonnServiceProvider ukelonn = new UkelonnServiceProvider(); + DataSource datasource = mock(DataSource.class); + when(datasource.getConnection()).thenThrow(SQLException.class); + MockLogService logservice = new MockLogService(); + ukelonn.setLogservice(logservice); + ukelonn.setDataSource(datasource); + ukelonn.activate(); + + // Verify that what we get with an SQL failure + // is an empty result and a warning in the log + List bonuses = ukelonn.modifyBonus(new Bonus()); + assertThat(bonuses).isEmpty(); + assertThat(logservice.getLogmessages()).isNotEmpty(); + assertThat(logservice.getLogmessages().get(0)).startsWith("[WARNING] Failed to update Bonus"); + } + + @SuppressWarnings("unchecked") + @Test + public void testDeleteBonusWithSQLException() throws Exception { + UkelonnServiceProvider ukelonn = new UkelonnServiceProvider(); + DataSource datasource = mock(DataSource.class); + when(datasource.getConnection()).thenThrow(SQLException.class); + MockLogService logservice = new MockLogService(); + ukelonn.setLogservice(logservice); + ukelonn.setDataSource(datasource); + ukelonn.activate(); + + // Verify that what we get with an SQL failure + // is an empty result and a warning in the log + List bonuses = ukelonn.deleteBonus(new Bonus()); + assertThat(bonuses).isEmpty(); + assertThat(logservice.getLogmessages()).isNotEmpty(); + assertThat(logservice.getLogmessages().get(0)).startsWith("[WARNING] Failed to delete Bonus"); + } + + private Bonus disableBonus(Bonus bonus) { + return new Bonus(bonus.getBonusId(), false, bonus.getIconurl(), bonus.getTitle(), bonus.getDescription(), bonus.getBonusFactor(), bonus.getStartDate(), bonus.getEndDate()); + } } diff --git a/ukelonn.db.liquibase.test/src/test/java/no/priv/bang/ukelonn/db/liquibase/test/TestLiquibaseRunnerTest.java b/ukelonn.db.liquibase.test/src/test/java/no/priv/bang/ukelonn/db/liquibase/test/TestLiquibaseRunnerTest.java index 83946eef..a22f5eb5 100644 --- a/ukelonn.db.liquibase.test/src/test/java/no/priv/bang/ukelonn/db/liquibase/test/TestLiquibaseRunnerTest.java +++ b/ukelonn.db.liquibase.test/src/test/java/no/priv/bang/ukelonn/db/liquibase/test/TestLiquibaseRunnerTest.java @@ -80,7 +80,7 @@ public void testPrepareDatabase() throws SQLException, DatabaseException { // Verify that the schema changeset as well as all of the test data change sets has been run List ranChangeSets = runner.getChangeLogHistory(datasource); - assertEquals(47, ranChangeSets.size()); + assertEquals(49, ranChangeSets.size()); } @Test diff --git a/ukelonn.db.liquibase/src/main/resources/ukelonn-db-changelog/db-changelog-1.0.2.xml b/ukelonn.db.liquibase/src/main/resources/ukelonn-db-changelog/db-changelog-1.0.2.xml index 980c3708..a14186c5 100644 --- a/ukelonn.db.liquibase/src/main/resources/ukelonn-db-changelog/db-changelog-1.0.2.xml +++ b/ukelonn.db.liquibase/src/main/resources/ukelonn-db-changelog/db-changelog-1.0.2.xml @@ -1,7 +1,7 @@ - + @@ -102,4 +102,43 @@ select sum(t.transaction_amount) as aggregate_amount, extract(year from t.transaction_time) as aggregate_year, extract(month from t.transaction_time) as aggregate_month, a.username as username from transactions t join transaction_types tt on tt.transaction_type_id=t.transaction_type_id join accounts a on a.account_id=t.account_id where tt.transaction_is_work group by extract(year from t.transaction_time), extract(month from t.transaction_time), a.username order by extract(year from t.transaction_time), extract(month from t.transaction_time) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ukelonn.db.liquibase/src/test/java/no/priv/bang/ukelonn/db/liquibase/UkelonnLiquibaseTest.java b/ukelonn.db.liquibase/src/test/java/no/priv/bang/ukelonn/db/liquibase/UkelonnLiquibaseTest.java index 533b6da7..b6cbe490 100644 --- a/ukelonn.db.liquibase/src/test/java/no/priv/bang/ukelonn/db/liquibase/UkelonnLiquibaseTest.java +++ b/ukelonn.db.liquibase/src/test/java/no/priv/bang/ukelonn/db/liquibase/UkelonnLiquibaseTest.java @@ -20,6 +20,8 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.Timestamp; +import java.util.Date; import java.util.Properties; import javax.sql.DataSource; @@ -60,6 +62,11 @@ public void testCreateSchema() throws Exception { assertEquals(0, count); } + + Date fromDate = new Date(); + Date toDate = new Date(); + createBonuses(connection, fromDate, toDate); + assertBonuses(connection, fromDate, toDate); } } @@ -84,6 +91,41 @@ public void testForceReleaseLocks() throws Exception { } } + private void createBonuses(Connection connection, Date startDate, Date endDate) throws Exception { + createBonus(connection, true, "Christmas bonus", "To finance presents", 2.0, startDate, endDate); + } + + private void createBonus(Connection connection, boolean enabled, String title, String description, double bonusFactor, Date startDate, Date endDate) throws Exception { + try (PreparedStatement statement = connection.prepareStatement("insert into bonuses (enabled, title, description, bonus_factor, start_date, end_date) values (?, ?, ?, ?, ?, ?)")) { + statement.setBoolean(1, enabled); + statement.setString(2, title); + statement.setString(3, description); + statement.setDouble(4, bonusFactor); + statement.setTimestamp(5, new Timestamp(startDate.toInstant().toEpochMilli())); + statement.setTimestamp(6, new Timestamp(endDate.toInstant().toEpochMilli())); + statement.executeUpdate(); + } + } + + private void assertBonuses(Connection connection, Date startDate, Date endDate) throws Exception { + try (PreparedStatement statement = connection.prepareStatement("select * from bonuses")) { + try(ResultSet results = statement.executeQuery()) { + assertBonus(results, true, "Christmas bonus", "To finance presents", 2.0, startDate, endDate); + } + } + } + + private void assertBonus(ResultSet results, boolean enabled, String title, String description, double bonusFactor, Date startDate, Date endDate) throws Exception { + assertTrue(results.next()); + assertEquals(enabled, results.getBoolean("enabled")); + assertNull(results.getString("iconurl")); + assertEquals(title, results.getString("title")); + assertEquals(description, results.getString("description")); + assertEquals(bonusFactor, results.getDouble("bonus_factor"), 0.0); + assertEquals(startDate, new Date(results.getTimestamp("start_date").getTime())); + assertEquals(endDate, new Date(results.getTimestamp("end_date").getTime())); + } + static private Connection createConnection() throws Exception { return dataSource.getConnection(); } diff --git a/ukelonn.services/src/main/java/no/priv/bang/ukelonn/UkelonnService.java b/ukelonn.services/src/main/java/no/priv/bang/ukelonn/UkelonnService.java index bf0b87d5..79fc5ec4 100644 --- a/ukelonn.services/src/main/java/no/priv/bang/ukelonn/UkelonnService.java +++ b/ukelonn.services/src/main/java/no/priv/bang/ukelonn/UkelonnService.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Steinar Bang + * Copyright 2016-2020 Steinar Bang * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import org.osgi.service.log.LogService; import no.priv.bang.ukelonn.beans.Account; +import no.priv.bang.ukelonn.beans.Bonus; import no.priv.bang.ukelonn.beans.Notification; import no.priv.bang.ukelonn.beans.PerformedTransaction; import no.priv.bang.ukelonn.beans.SumYear; @@ -87,4 +88,14 @@ public interface UkelonnService { List earningsSumOverMonth(String username); + List getActiveBonuses(); + + List getAllBonuses(); + + List createBonus(Bonus newBonus); + + List modifyBonus(Bonus updatedBonus); + + List deleteBonus(Bonus removedBonus); + } diff --git a/ukelonn.services/src/main/java/no/priv/bang/ukelonn/beans/Bonus.java b/ukelonn.services/src/main/java/no/priv/bang/ukelonn/beans/Bonus.java new file mode 100644 index 00000000..caca2bbd --- /dev/null +++ b/ukelonn.services/src/main/java/no/priv/bang/ukelonn/beans/Bonus.java @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Steinar Bang + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations + * under the License. + */ +package no.priv.bang.ukelonn.beans; + +import java.util.Date; + +import no.priv.bang.beans.immutable.Immutable; + +public class Bonus extends Immutable { // NOSONAR Immutable handles added fields + int bonusId; + private boolean enabled; + private String iconurl; + private String title; + private String description; + private double bonusFactor; + private Date startDate; + private Date endDate; + + public Bonus(int bonusId, boolean enabled, String iconurl, String title, String description, double bonusFactor, Date startDate, Date endDate) { // NOSONAR + this.bonusId = bonusId; + this.enabled = enabled; + this.iconurl = iconurl; + this.title = title; + this.description = description; + this.bonusFactor = bonusFactor; + this.startDate = startDate; + this.endDate = endDate; + } + + public Bonus() { + // jackson require a no-args constructor + } + + public int getBonusId() { + return bonusId; + } + + public boolean isEnabled() { + return enabled; + } + + public String getIconurl() { + return iconurl; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public double getBonusFactor() { + return bonusFactor; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + +} diff --git a/ukelonn.services/src/test/java/no/priv/bang/ukelonn/beans/BonusTest.java b/ukelonn.services/src/test/java/no/priv/bang/ukelonn/beans/BonusTest.java new file mode 100644 index 00000000..50786f8f --- /dev/null +++ b/ukelonn.services/src/test/java/no/priv/bang/ukelonn/beans/BonusTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Steinar Bang + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations + * under the License. + */ +package no.priv.bang.ukelonn.beans; + +import static org.junit.Assert.*; + +import java.util.Date; + +import org.junit.Test; + +public class BonusTest { + + @Test + public void testCreate() { + int bonusId = 1; + boolean enabled = true; + String iconurl = "http//images.com/juletre.jpg"; + String title = "Julebonus"; + String description = "Dobbel betaling for jobber"; + double bonusFactor = 2.0; + Date startDate = new Date(); + Date endDate = new Date(); + Bonus bean = new Bonus(bonusId, enabled, iconurl, title, description, bonusFactor, startDate, endDate); + assertEquals(bonusId, bean.getBonusId()); + assertTrue(bean.isEnabled()); + assertEquals(iconurl, bean.getIconurl()); + assertEquals(title, bean.getTitle()); + assertEquals(description, bean.getDescription()); + assertEquals(bonusFactor, bean.getBonusFactor(), 0.0); + assertEquals(startDate, bean.getStartDate()); + assertEquals(endDate, bean.getEndDate()); + } + + @Test + public void testNoArgsConstructor() { + Bonus bean = new Bonus(); + assertEquals(0, bean.getBonusId()); + assertFalse(bean.isEnabled()); + assertNull(bean.getIconurl()); + assertNull(bean.getTitle()); + assertNull(bean.getDescription()); + assertEquals(0.0, bean.getBonusFactor(), 0.0); + assertNull(bean.getStartDate()); + assertNull(bean.getEndDate()); + } + +} diff --git a/ukelonn.web.frontend/src/main/frontend/actiontypes.js b/ukelonn.web.frontend/src/main/frontend/actiontypes.js index 5e802c12..7496d5b0 100644 --- a/ukelonn.web.frontend/src/main/frontend/actiontypes.js +++ b/ukelonn.web.frontend/src/main/frontend/actiontypes.js @@ -77,6 +77,20 @@ export const CREATE_USER_FAILURE = createAction('CREATE_USER_FAILURE'); export const MODIFY_USER_PASSWORD_REQUEST = createAction('MODIFY_USER_PASSWORD_REQUEST'); export const MODIFY_USER_PASSWORD_RECEIVE = createAction('MODIFY_USER_PASSWORD_RECEIVE'); export const MODIFY_USER_PASSWORD_FAILURE = createAction('MODIFY_USER_PASSWORD_FAILURE'); +export const GET_ACTIVE_BONUSES = createAction('GET_ACTIVE_BONUSES'); +export const RECEIVE_ACTIVE_BONUSES = createAction('RECEIVE_ACTIVE_BONUSES'); +export const RECEIVE_ACTIVE_BONUSES_FAILURE = createAction('RECEIVE_ACTIVE_BONUSES_FAILURE'); +export const GET_ALL_BONUSES = createAction('GET_ALL_BONUSES'); +export const GET_ALL_BONUSES_FAILURE = createAction('GET_ALL_BONUSES_FAILURE'); +export const RECEIVE_ALL_BONUSES = createAction('RECEIVE_ALL_BONUSES'); +export const RECEIVE_ALL_BONUSES_FAILURE = createAction('RECEIVE_ALL_BONUSES_FAILURE'); +export const UPDATE_BONUS = createAction('UPDATE_BONUS'); +export const CREATE_BONUS = createAction('CREATE_BONUS'); +export const CREATE_BONUS_FAILURE = createAction('CREATE_BONUS_FAILURE'); +export const MODIFY_BONUS = createAction('MODIFY_BONUS'); +export const MODIFY_BONUS_FAILURE = createAction('MODIFY_BONUS_FAILURE'); +export const DELETE_BONUS = createAction('DELETE_BONUS'); +export const DELETE_BONUS_FAILURE = createAction('DELETE_BONUS_FAILURE'); export const START_NOTIFICATION_LISTENING = createAction('START_NOTIFICATION_LISTENING'); export const RECEIVED_NOTIFICATION = createAction('RECEIVED_NOTIFICATION'); export const ERROR_RECEIVED_NOTIFICATION = createAction('ERROR_RECEIVED_NOTIFICATION'); diff --git a/ukelonn.web.frontend/src/main/frontend/components/Admin.js b/ukelonn.web.frontend/src/main/frontend/components/Admin.js index b319d3fd..b86fd39b 100644 --- a/ukelonn.web.frontend/src/main/frontend/components/Admin.js +++ b/ukelonn.web.frontend/src/main/frontend/components/Admin.js @@ -12,6 +12,7 @@ import { REGISTERPAYMENT_REQUEST, } from '../actiontypes'; import { emptyAccount } from '../constants'; +import BonusBanner from './BonusBanner'; import Accounts from './Accounts'; import Paymenttypes from './Paymenttypes'; import Amount from './Amount'; @@ -51,6 +52,7 @@ function Admin(props) {

Registrer betaling

+
{ e.preventDefault(); }}>
@@ -119,6 +121,11 @@ function Admin(props) {   + + Administrer bonuser +   + +

diff --git a/ukelonn.web.frontend/src/main/frontend/components/AdminBonusCreate.js b/ukelonn.web.frontend/src/main/frontend/components/AdminBonusCreate.js new file mode 100644 index 00000000..d76fa8f6 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/components/AdminBonusCreate.js @@ -0,0 +1,138 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router'; +import { Link } from 'react-router-dom'; +import DatePicker from 'react-datepicker'; +import { userIsNotLoggedIn } from '../common/login'; +import { + LOGOUT_REQUEST, + UPDATE_BONUS, + CREATE_BONUS, +} from '../actiontypes'; +import { emptyBonus } from '../constants'; + +function AdminBonusCreate(props) { + if (userIsNotLoggedIn(props)) { + return ; + } + + const { + bonuses, + bonus, + onUpdateEnabled, + onUpdateIconurl, + onUpdateTitle, + onUpdateDescription, + onUpdateBonusFactor, + onUpdateStartDate, + onUpdateEndDate, + onCreateBonus, + onLogout, + } = props; + const enabled = bonus.enabled; + const iconurl = bonus.iconurl || ''; + const title = bonus.title || ''; + const description = bonus.description || ''; + const bonusFactor = bonus.bonusFactor || 0; + const startDate = bonus.startDate; + const endDate = bonus.endDate; + + return ( +
+ + +   + Administer bonuser + +
+
+

Lag ny bonus

+
+
+ { e.preventDefault(); }}> +
+
+ +
+ onUpdateEnabled(bonus, e)} /> +
+
+
+ +
+ onUpdateIconurl(bonus, e)} /> +
+
+
+ +
+ onUpdateTitle(bonus, e)} /> +
+
+
+ +
+ onUpdateDescription(bonus, e)} /> +
+
+
+ +
+ onUpdateBonusFactor(bonus, e)} /> +
+
+
+ +
+ onUpdateStartDate(bonus, d)} readOnly={true} /> +
+
+
+ +
+ onUpdateEndDate(bonus, d)} readOnly={true} /> +
+
+
+
+
+ +
+
+
+ +
+ +
+ Tilbake til topp +
+ ); +}; + +function mapStateToProps(state) { + return { + haveReceivedResponseFromLogin: state.haveReceivedResponseFromLogin, + loginResponse: state.loginResponse, + allbonuses: state.allbonuses, + bonus: state.bonus, + }; +} + +function mapDispatchToProps(dispatch) { + return { + onUpdateEnabled: (bonus, e) => dispatch(UPDATE_BONUS({ ...bonus, enabled: e.target.checked })), + onUpdateIconurl: (bonus, e) => dispatch(UPDATE_BONUS({ ...bonus, iconurl: e.target.value })), + onUpdateTitle: (bonus, e) => dispatch(UPDATE_BONUS({ ...bonus, title: e.target.value })), + onUpdateDescription: (bonus, e) => dispatch(UPDATE_BONUS({ ...bonus, description: e.target.value })), + onUpdateBonusFactor: (bonus, e) => dispatch(UPDATE_BONUS({ ...bonus, bonusFactor: e.target.value })), + onUpdateStartDate: (bonus, startDate) => dispatch(UPDATE_BONUS({ ...bonus, startDate })), + onUpdateEndDate: (bonus, endDate) => dispatch(UPDATE_BONUS({ ...bonus, endDate })), + onCreateBonus: bonus => { + dispatch(CREATE_BONUS(bonus)); + dispatch(UPDATE_BONUS({ ...emptyBonus })); + }, + onLogout: () => dispatch(LOGOUT_REQUEST()), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(AdminBonusCreate); diff --git a/ukelonn.web.frontend/src/main/frontend/components/AdminBonusDelete.js b/ukelonn.web.frontend/src/main/frontend/components/AdminBonusDelete.js new file mode 100644 index 00000000..c1aa5796 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/components/AdminBonusDelete.js @@ -0,0 +1,109 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router'; +import { Link } from 'react-router-dom'; +import { userIsNotLoggedIn } from '../common/login'; +import { + LOGOUT_REQUEST, + UPDATE_BONUS, + DELETE_BONUS, +} from '../actiontypes'; +import { emptyBonus } from '../constants'; + +function reloadJobListWhenAccountHasChanged(oldAccount, newAccount, loadBonuses) { + if (oldAccount !== newAccount) { + loadBonuses(newAccount); + } +} + +function AdminBonusesDelete(props) { + if (userIsNotLoggedIn(props)) { + return ; + } + + let { + allbonuses, + bonus, + onUpdateBonus, + onDeleteBonus, + onLogout, + } = props; + const bonuses = [emptyBonus].concat(allbonuses); + const bonusId = bonus.bonusId; + const title = bonus.title || ''; + const description = bonus.description || ''; + + return ( +
+ + +   + Administer bonuser + +
+
+

Slett bonuser

+
+
+ +
{ e.preventDefault(); }}> +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+ +
+ +
+ Tilbake til topp +
+ ); +} + +function mapStateToProps(state) { + return { + haveReceivedResponseFromLogin: state.haveReceivedResponseFromLogin, + loginResponse: state.loginResponse, + allbonuses: state.allbonuses, + bonus: state.bonus || {}, + }; +} + +function mapDispatchToProps(dispatch) { + return { + onUpdateBonus: bonus => dispatch(UPDATE_BONUS(bonus)), + onDeleteBonus: bonus => { + if (parseInt(bonus.bonusId) !== emptyBonus.bonusId) { + dispatch(DELETE_BONUS(bonus)); + dispatch(UPDATE_BONUS({ ...emptyBonus })); + } + }, + onLogout: () => dispatch(LOGOUT_REQUEST()), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(AdminBonusesDelete); diff --git a/ukelonn.web.frontend/src/main/frontend/components/AdminBonusModify.js b/ukelonn.web.frontend/src/main/frontend/components/AdminBonusModify.js new file mode 100644 index 00000000..9c5eee94 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/components/AdminBonusModify.js @@ -0,0 +1,151 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router'; +import { Link } from 'react-router-dom'; +import DatePicker from 'react-datepicker'; +import moment from 'moment'; +import { userIsNotLoggedIn } from '../common/login'; +import { + LOGOUT_REQUEST, + UPDATE_BONUS, + MODIFY_BONUS, +} from '../actiontypes'; +import { emptyBonus } from '../constants'; + +function AdminBonusesModify(props) { + if (userIsNotLoggedIn(props)) { + return ; + } + + let { + allbonuses, + bonus, + onUpdateBonus, + onUpdateEnabled, + onUpdateIconurl, + onUpdateTitle, + onUpdateDescription, + onUpdateBonusFactor, + onUpdateStartDate, + onUpdateEndDate, + onModifyBonus, + onLogout, + } = props; + const bonuses = [emptyBonus].concat(allbonuses); + const bonusId = bonus.bonusId; + const enabled = bonus.enabled; + const iconurl = bonus.iconurl || ''; + const title = bonus.title || ''; + const description = bonus.description || ''; + const bonusFactor = bonus.bonusFactor || 0; + const startDate = moment(bonus.startDate); + const endDate = moment(bonus.endDate); + + return ( +
+ + +   + Administer bonuser + +
+
+

Endre bonuser

+
+
+
{ e.preventDefault(); }}> +
+
+ +
+ +
+
+
+ +
+ onUpdateEnabled(bonus, e)} /> +
+
+
+ +
+ onUpdateIconurl(bonus, e)} /> +
+
+
+ +
+ onUpdateTitle(bonus, e)} /> +
+
+
+ +
+ onUpdateDescription(bonus, e)} /> +
+
+
+ +
+ onUpdateBonusFactor(bonus, e)} /> +
+
+
+ +
+ onUpdateStartDate(bonus, d)} readOnly={true} /> +
+
+
+ +
+ onUpdateEndDate(bonus, d)} readOnly={true} /> +
+
+
+
+
+ +
+
+
+ +
+ +
+ ); +} + +function mapStateToProps(state) { + return { + haveReceivedResponseFromLogin: state.haveReceivedResponseFromLogin, + loginResponse: state.loginResponse, + allbonuses: state.allbonuses, + bonus: state.bonus || {}, + }; +} + +function mapDispatchToProps(dispatch) { + return { + onUpdateBonus: bonus => dispatch(UPDATE_BONUS(bonus)), + onUpdateEnabled: (bonus, e) => dispatch(UPDATE_BONUS({ ...bonus, enabled: e.target.checked })), + onUpdateIconurl: (bonus, e) => dispatch(UPDATE_BONUS({ ...bonus, iconurl: e.target.value })), + onUpdateTitle: (bonus, e) => dispatch(UPDATE_BONUS({ ...bonus, title: e.target.value })), + onUpdateDescription: (bonus, e) => dispatch(UPDATE_BONUS({ ...bonus, description: e.target.value })), + onUpdateBonusFactor: (bonus, e) => dispatch(UPDATE_BONUS({ ...bonus, bonusFactor: e.target.value })), + onUpdateStartDate: (bonus, startDate) => dispatch(UPDATE_BONUS({ ...bonus, startDate })), + onUpdateEndDate: (bonus, endDate) => dispatch(UPDATE_BONUS({ ...bonus, endDate })), + onModifyBonus: bonus => { + if (parseInt(bonus.bonusId) !== emptyBonus.bonusId) { + dispatch(MODIFY_BONUS(bonus)); + dispatch(UPDATE_BONUS({ ...emptyBonus })); + } + }, + onLogout: () => dispatch(LOGOUT_REQUEST()), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(AdminBonusesModify); diff --git a/ukelonn.web.frontend/src/main/frontend/components/AdminBonuses.js b/ukelonn.web.frontend/src/main/frontend/components/AdminBonuses.js new file mode 100644 index 00000000..807bb8d1 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/components/AdminBonuses.js @@ -0,0 +1,68 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router'; +import { Link } from 'react-router-dom'; +import { userIsNotLoggedIn } from '../common/login'; +import { + LOGOUT_REQUEST, +} from '../actiontypes'; + +function AdminBonuses(props) { + if (userIsNotLoggedIn(props)) { + return ; + } + + let { onLogout } = props; + + return ( +
+ + +   + Register betaling + +
+
+

Administrer bonuser

+
+
+
+ + Endre bonuser +   + + + + Lag ny bonus +   + + + + Slett bonus +   + + +
+
+
+ +
+ Tilbake til topp +
+ ); +}; + +function mapStateToProps(state) { + return { + haveReceivedResponseFromLogin: state.haveReceivedResponseFromLogin, + loginResponse: state.loginResponse, + }; +} + +function mapDispatchToProps(dispatch) { + return { + onLogout: () => dispatch(LOGOUT_REQUEST()), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(AdminBonuses); diff --git a/ukelonn.web.frontend/src/main/frontend/components/App.js b/ukelonn.web.frontend/src/main/frontend/components/App.js index cc3069fa..106e725e 100644 --- a/ukelonn.web.frontend/src/main/frontend/components/App.js +++ b/ukelonn.web.frontend/src/main/frontend/components/App.js @@ -25,6 +25,10 @@ import AdminPaymenttypesCreate from './AdminPaymenttypesCreate'; import AdminUsers from './AdminUsers'; import AdminUsersModify from './AdminUsersModify'; import AdminUsersCreate from './AdminUsersCreate'; +import AdminBonusModify from './AdminBonusModify'; +import AdminBonusCreate from './AdminBonusCreate'; +import AdminBonusDelete from './AdminBonusDelete'; +import AdminBonuses from './AdminBonuses'; import AdminUsersChangePassword from './AdminUsersChangePassword'; import Bootstrap from 'bootstrap/dist/css/bootstrap.min.css'; import '../ukelonn.css'; @@ -55,6 +59,10 @@ function App(props) { + + + + diff --git a/ukelonn.web.frontend/src/main/frontend/components/BonusBanner.js b/ukelonn.web.frontend/src/main/frontend/components/BonusBanner.js new file mode 100644 index 00000000..651c6300 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/components/BonusBanner.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import moment from 'moment'; + +function BonusBanner(props) { + const { activebonuses } = props; + if (!activebonuses.length) { + return null; + } + + return ( +
+ {activebonuses.map(renderBonus)} +
+ ); +} + +function mapStateToProps(state) { + const activebonuses = state.activebonuses || []; + return { + activebonuses, + }; +} + +export default connect(mapStateToProps)(BonusBanner); + +function renderBonus(bonus, idx) { + const key = 'bonus' + idx.toString(); + const daysRemaining = moment(bonus.endDate).diff(moment(), 'days'); + if (!bonus.iconurl) { + return ( +
+
{bonus.title} aktiv! ({daysRemaining} dager igjen)
+
{bonus.description}
+
+ ); + } + + return ( +
+
+
+
{bonus.title} aktiv! ({daysRemaining} dager igjen)
+
{bonus.description}
+
+
+ ); +} diff --git a/ukelonn.web.frontend/src/main/frontend/components/User.js b/ukelonn.web.frontend/src/main/frontend/components/User.js index 21db1fa6..990dfa98 100644 --- a/ukelonn.web.frontend/src/main/frontend/components/User.js +++ b/ukelonn.web.frontend/src/main/frontend/components/User.js @@ -12,6 +12,7 @@ import { UPDATE_PERFORMEDJOB, REGISTERJOB_REQUEST, } from '../actiontypes'; +import BonusBanner from './BonusBanner'; import Jobtypes from './Jobtypes'; import Notification from './Notification'; import EarningsMessage from './EarningsMessage'; @@ -38,6 +39,7 @@ function User(props) {
+
diff --git a/ukelonn.web.frontend/src/main/frontend/constants.js b/ukelonn.web.frontend/src/main/frontend/constants.js index 91a80ab0..f1ad3f02 100644 --- a/ukelonn.web.frontend/src/main/frontend/constants.js +++ b/ukelonn.web.frontend/src/main/frontend/constants.js @@ -1,3 +1,5 @@ +import moment from 'moment'; + export const emptyAccount = { accountId: -1, username: '', @@ -6,3 +8,14 @@ export const emptyAccount = { fullName: '', balance: 0.0, }; + +export const emptyBonus = { + bonusId: -1, + enabled: false, + iconurl: '', + title: '', + description: '', + bonusFactor: 1.0, + startDate: moment(), + endDate: moment(), +}; diff --git a/ukelonn.web.frontend/src/main/frontend/reducers/activebonusesReducer.js b/ukelonn.web.frontend/src/main/frontend/reducers/activebonusesReducer.js new file mode 100644 index 00000000..71ae0330 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/reducers/activebonusesReducer.js @@ -0,0 +1,10 @@ +import { createReducer } from '@reduxjs/toolkit'; +import { + RECEIVE_ACTIVE_BONUSES, +} from '../actiontypes'; + +const activebonusesReducer = createReducer([], { + [RECEIVE_ACTIVE_BONUSES]: (state, action) => action.payload, +}); + +export default activebonusesReducer; diff --git a/ukelonn.web.frontend/src/main/frontend/reducers/allbonusesReducer.js b/ukelonn.web.frontend/src/main/frontend/reducers/allbonusesReducer.js new file mode 100644 index 00000000..cb621580 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/reducers/allbonusesReducer.js @@ -0,0 +1,10 @@ +import { createReducer } from '@reduxjs/toolkit'; +import { + RECEIVE_ALL_BONUSES, +} from '../actiontypes'; + +const allbonusesReducer = createReducer([], { + [RECEIVE_ALL_BONUSES]: (state, action) => action.payload, +}); + +export default allbonusesReducer; diff --git a/ukelonn.web.frontend/src/main/frontend/reducers/bonusReducer.js b/ukelonn.web.frontend/src/main/frontend/reducers/bonusReducer.js new file mode 100644 index 00000000..b372d1e6 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/reducers/bonusReducer.js @@ -0,0 +1,11 @@ +import { createReducer } from '@reduxjs/toolkit'; +import { + UPDATE_BONUS, + RECEIVE_ALL_BONUSES, +} from '../actiontypes'; + +const bonusReducer = createReducer({}, { + [UPDATE_BONUS]: (state, action) => ({ ...state, ...action.payload }), +}); + +export default bonusReducer; diff --git a/ukelonn.web.frontend/src/main/frontend/reducers/index.js b/ukelonn.web.frontend/src/main/frontend/reducers/index.js index 26ce80c7..f7d61a17 100644 --- a/ukelonn.web.frontend/src/main/frontend/reducers/index.js +++ b/ukelonn.web.frontend/src/main/frontend/reducers/index.js @@ -20,6 +20,9 @@ import usersReducer from './usersReducer'; import usernamesReducer from './usernamesReducer'; import userReducer from './userReducer'; import passwordsReducer from './passwordsReducer'; +import activebonusesReducer from './activebonusesReducer'; +import allbonusesReducer from './allbonusesReducer'; +import bonusReducer from './bonusReducer'; import earningsSumOverYearReducer from './earningsSumOverYearReducer'; import earningsSumOverMonthReducer from './earningsSumOverMonthReducer'; @@ -45,6 +48,9 @@ export default (history) => combineReducers({ usernames: usernamesReducer, user: userReducer, passwords: passwordsReducer, + activebonuses: activebonusesReducer, + allbonuses: allbonusesReducer, + bonus: bonusReducer, earningsSumOverYear: earningsSumOverYearReducer, earningsSumOverMonth: earningsSumOverMonthReducer, }); diff --git a/ukelonn.web.frontend/src/main/frontend/sagas/activebonusesSaga.js b/ukelonn.web.frontend/src/main/frontend/sagas/activebonusesSaga.js new file mode 100644 index 00000000..0be5b7b0 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/sagas/activebonusesSaga.js @@ -0,0 +1,27 @@ +import { takeLatest, call, put, fork } from 'redux-saga/effects'; +import axios from 'axios'; +import { + GET_ACTIVE_BONUSES, + RECEIVE_ACTIVE_BONUSES, + RECEIVE_ACTIVE_BONUSES_FAILURE, +} from '../actiontypes'; + +// watcher saga +export function* requestActivebonusesSaga() { + yield takeLatest(GET_ACTIVE_BONUSES, receiveActivebonusesSaga); +} + +function doActivebonuses() { + return axios.get('/ukelonn/api/activebonuses'); +} + +// worker saga +function* receiveActivebonusesSaga(action) { + try { + const response = yield call(doActivebonuses); + const activebonuses = (response.headers['content-type'] == 'application/json') ? response.data : []; + yield put(RECEIVE_ACTIVE_BONUSES(activebonuses)); + } catch (error) { + yield put(RECEIVE_ACTIVE_BONUSES_FAILURE(error)); + } +} diff --git a/ukelonn.web.frontend/src/main/frontend/sagas/allbonusesSaga.js b/ukelonn.web.frontend/src/main/frontend/sagas/allbonusesSaga.js new file mode 100644 index 00000000..b220e7bd --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/sagas/allbonusesSaga.js @@ -0,0 +1,27 @@ +import { takeLatest, call, put, fork } from 'redux-saga/effects'; +import axios from 'axios'; +import { + GET_ALL_BONUSES, + RECEIVE_ALL_BONUSES, + RECEIVE_ALL_BONUSES_FAILURE, +} from '../actiontypes'; + +// watcher saga +export function* requestAllbonusesSaga() { + yield takeLatest(GET_ALL_BONUSES, receiveAllbonusesSaga); +} + +function doAllbonuses() { + return axios.get('/ukelonn/api/allbonuses'); +} + +// worker saga +function* receiveAllbonusesSaga(action) { + try { + const response = yield call(doAllbonuses); + const allbonuses = (response.headers['content-type'] == 'application/json') ? response.data : []; + yield put(RECEIVE_ALL_BONUSES(allbonuses)); + } catch (error) { + yield put(RECEIVE_ALL_BONUSES_FAILURE(error)); + } +} diff --git a/ukelonn.web.frontend/src/main/frontend/sagas/createbonusSaga.js b/ukelonn.web.frontend/src/main/frontend/sagas/createbonusSaga.js new file mode 100644 index 00000000..256154a0 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/sagas/createbonusSaga.js @@ -0,0 +1,27 @@ +import { takeLatest, call, put, fork } from 'redux-saga/effects'; +import axios from 'axios'; +import { + CREATE_BONUS, + RECEIVE_ALL_BONUSES, + CREATE_BONUS_FAILURE, +} from '../actiontypes'; + +// watcher saga +export default function* createbonusSaga() { + yield takeLatest(CREATE_BONUS, receiveCreateBonusSaga); +} + +function doCreateBonus(bonus) { + return axios.post('/ukelonn/api/admin/createbonus', bonus); +} + +// worker saga +function* receiveCreateBonusSaga(action) { + try { + const response = yield call(doCreateBonus, action.payload); + const bonuses = (response.headers['content-type'] === 'application/json') ? response.data : []; + yield put(RECEIVE_ALL_BONUSES(bonuses)); + } catch (error) { + yield put(CREATE_BONUS_FAILURE(error)); + } +} diff --git a/ukelonn.web.frontend/src/main/frontend/sagas/deletebonusSaga.js b/ukelonn.web.frontend/src/main/frontend/sagas/deletebonusSaga.js new file mode 100644 index 00000000..791e4098 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/sagas/deletebonusSaga.js @@ -0,0 +1,27 @@ +import { takeLatest, call, put, fork } from 'redux-saga/effects'; +import axios from 'axios'; +import { + DELETE_BONUS, + RECEIVE_ALL_BONUSES, + DELETE_BONUS_FAILURE, +} from '../actiontypes'; + +// watcher saga +export default function* deleteBonusSaga() { + yield takeLatest(DELETE_BONUS, receiveDeleteBonusSaga); +} + +function doDeleteBonus(bonus) { + return axios.post('/ukelonn/api/admin/deletebonus', bonus); +} + +// worker saga +function* receiveDeleteBonusSaga(action) { + try { + const response = yield call(doDeleteBonus, action.payload); + const bonuses = (response.headers['content-type'] === 'application/json') ? response.data : []; + yield put(RECEIVE_ALL_BONUSES(bonuses)); + } catch (error) { + yield put(DELETE_BONUS_FAILURE(error)); + } +} diff --git a/ukelonn.web.frontend/src/main/frontend/sagas/index.js b/ukelonn.web.frontend/src/main/frontend/sagas/index.js index f0cc66e3..a612cca7 100644 --- a/ukelonn.web.frontend/src/main/frontend/sagas/index.js +++ b/ukelonn.web.frontend/src/main/frontend/sagas/index.js @@ -19,6 +19,11 @@ import { requestModifyPaymenttypeSaga } from './modifypaymenttypeSaga'; import { requestUsersSaga } from './usersSaga'; import { requestModifyUserSaga } from './modifyuserSaga'; import { requestCreateUserSaga } from './createuserSaga'; +import { requestActivebonusesSaga } from './activebonusesSaga'; +import { requestAllbonusesSaga } from './allbonusesSaga'; +import modifybonusSaga from './modifybonusSaga'; +import createbonusSaga from './createbonusSaga'; +import deletebonusSaga from './deletebonusSaga'; import { requestChangePasswordSaga } from './modifyuserpasswordSaga'; import { startNotificationListening } from './notificationSaga'; import earningsSumOverYearSaga from './earningsSumOverYearSaga'; @@ -46,6 +51,11 @@ export function* rootSaga() { fork(requestCreatePaymenttypeSaga), fork(requestUsersSaga), fork(requestModifyUserSaga), + fork(requestActivebonusesSaga), + fork(requestAllbonusesSaga), + fork(modifybonusSaga), + fork(createbonusSaga), + fork(deletebonusSaga), fork(requestCreateUserSaga), fork(requestChangePasswordSaga), fork(startNotificationListening), diff --git a/ukelonn.web.frontend/src/main/frontend/sagas/locationSaga.js b/ukelonn.web.frontend/src/main/frontend/sagas/locationSaga.js index ba05c5e7..0d514371 100644 --- a/ukelonn.web.frontend/src/main/frontend/sagas/locationSaga.js +++ b/ukelonn.web.frontend/src/main/frontend/sagas/locationSaga.js @@ -10,7 +10,11 @@ import { JOBTYPELIST_REQUEST, RECENTJOBS_REQUEST, RECENTPAYMENTS_REQUEST, + GET_ACTIVE_BONUSES, + GET_ALL_BONUSES, + UPDATE_BONUS, } from '../actiontypes'; +import { emptyBonus } from '../constants'; function* locationChange(action) { const { location = {} } = action.payload || {}; @@ -21,6 +25,7 @@ function* locationChange(action) { yield put(ACCOUNT_REQUEST(username)); yield put(START_NOTIFICATION_LISTENING(username)); yield put(JOBTYPELIST_REQUEST()); + yield put(GET_ACTIVE_BONUSES()); } if (pathname === '/ukelonn/performedjobs') { @@ -46,6 +51,7 @@ function* locationChange(action) { if (pathname === '/ukelonn/admin') { yield put(ACCOUNTS_REQUEST()); yield put(PAYMENTTYPES_REQUEST()); + yield put(GET_ACTIVE_BONUSES()); } if (pathname === '/ukelonn/admin/jobtypes/modify' || pathname === '/ukelonn/admin/jobtypes/create') { @@ -72,6 +78,20 @@ function* locationChange(action) { if (pathname === '/ukelonn/admin/users/modify' || pathname === '/ukelonn/admin/users/password' || pathname === '/ukelonn/admin/users/create') { yield put(USERS_REQUEST()); } + + if (pathname === '/ukelonn/admin/bonuses/create') { + yield put(UPDATE_BONUS(emptyBonus)); + } + + if (pathname === '/ukelonn/admin/bonuses/modify') { + yield put(GET_ALL_BONUSES()); + yield put(UPDATE_BONUS(emptyBonus)); + } + + if (pathname === '/ukelonn/admin/bonuses/delete') { + yield put(GET_ALL_BONUSES()); + yield put(UPDATE_BONUS(emptyBonus)); + } } export default function* locationSaga() { diff --git a/ukelonn.web.frontend/src/main/frontend/sagas/modifybonusSaga.js b/ukelonn.web.frontend/src/main/frontend/sagas/modifybonusSaga.js new file mode 100644 index 00000000..52d191a0 --- /dev/null +++ b/ukelonn.web.frontend/src/main/frontend/sagas/modifybonusSaga.js @@ -0,0 +1,27 @@ +import { takeLatest, call, put, fork } from 'redux-saga/effects'; +import axios from 'axios'; +import { + MODIFY_BONUS, + RECEIVE_ALL_BONUSES, + MODIFY_BONUS_FAILURE, +} from '../actiontypes'; + +// watcher saga +export default function* modifyBonusSaga() { + yield takeLatest(MODIFY_BONUS, receiveModifyBonusSaga); +} + +function doModifyBonus(bonus) { + return axios.post('/ukelonn/api/admin/modifybonus', bonus); +} + +// worker saga +function* receiveModifyBonusSaga(action) { + try { + const response = yield call(doModifyBonus, action.payload); + const bonuses = (response.headers['content-type'] === 'application/json') ? response.data : []; + yield put(RECEIVE_ALL_BONUSES(bonuses)); + } catch (error) { + yield put(MODIFY_BONUS_FAILURE(error)); + } +} diff --git a/ukelonn.web.frontend/src/main/java/no/priv/bang/ukelonn/web/frontend/UkelonnServlet.java b/ukelonn.web.frontend/src/main/java/no/priv/bang/ukelonn/web/frontend/UkelonnServlet.java index 19416e21..033a47a5 100644 --- a/ukelonn.web.frontend/src/main/java/no/priv/bang/ukelonn/web/frontend/UkelonnServlet.java +++ b/ukelonn.web.frontend/src/main/java/no/priv/bang/ukelonn/web/frontend/UkelonnServlet.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 Steinar Bang + * Copyright 2016-2020 Steinar Bang * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,6 +59,10 @@ public UkelonnServlet() { "/admin/users/modify", "/admin/users/password", "/admin/users", + "/admin/bonuses/modify", + "/admin/bonuses/create", + "/admin/bonuses/delete", + "/admin/bonuses", "/admin"); } diff --git a/ukelonn.web.services/pom.xml b/ukelonn.web.services/pom.xml index ba011398..6c692ca8 100644 --- a/ukelonn.web.services/pom.xml +++ b/ukelonn.web.services/pom.xml @@ -90,6 +90,12 @@ no.priv.bang.beans beans.immutable + + org.apache.commons + commons-lang3 + 3.9 + test + no.priv.bang.osgiservice osgiservice.users diff --git a/ukelonn.web.services/src/main/java/no/priv/bang/ukelonn/api/resources/Bonuses.java b/ukelonn.web.services/src/main/java/no/priv/bang/ukelonn/api/resources/Bonuses.java new file mode 100644 index 00000000..686429a2 --- /dev/null +++ b/ukelonn.web.services/src/main/java/no/priv/bang/ukelonn/api/resources/Bonuses.java @@ -0,0 +1,72 @@ +/* + * Copyright 2020 Steinar Bang + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations + * under the License. + */ +package no.priv.bang.ukelonn.api.resources; + +import java.util.List; + +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import no.priv.bang.ukelonn.UkelonnService; +import no.priv.bang.ukelonn.beans.Bonus; + +@Path("") +@Produces(MediaType.APPLICATION_JSON) +public class Bonuses { + + @Inject + UkelonnService ukelonn; + + @GET + @Path("activebonuses") + public List getActiveBonuses() { + return ukelonn.getActiveBonuses(); + } + + @GET + @Path("allbonuses") + public List getAllBonuses() { + return ukelonn.getAllBonuses(); + } + + @POST + @Path("/admin/createbonus") + @Consumes(MediaType.APPLICATION_JSON) + public List createBonus(Bonus bonus) { + return ukelonn.createBonus(bonus); + } + + + @POST + @Path("/admin/modifybonus") + @Consumes(MediaType.APPLICATION_JSON) + public List modifyBonus(Bonus bonus) { + return ukelonn.modifyBonus(bonus); + } + + @POST + @Path("/admin/deletebonus") + @Consumes(MediaType.APPLICATION_JSON) + public List deleteBonus(Bonus bonus) { + return ukelonn.deleteBonus(bonus); + } + +} diff --git a/ukelonn.web.services/src/test/java/no/priv/bang/ukelonn/api/UkelonnRestApiServletTest.java b/ukelonn.web.services/src/test/java/no/priv/bang/ukelonn/api/UkelonnRestApiServletTest.java index d97861db..f3ba2e3f 100644 --- a/ukelonn.web.services/src/test/java/no/priv/bang/ukelonn/api/UkelonnRestApiServletTest.java +++ b/ukelonn.web.services/src/test/java/no/priv/bang/ukelonn/api/UkelonnRestApiServletTest.java @@ -34,6 +34,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import javax.ws.rs.core.MediaType; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.util.ThreadContext; @@ -41,8 +42,11 @@ import org.glassfish.jersey.server.ServerProperties; import org.junit.Ignore; import org.junit.Test; +import org.osgi.service.log.LogService; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.mockrunner.mock.web.MockHttpServletRequest; import com.mockrunner.mock.web.MockHttpServletResponse; import com.mockrunner.mock.web.MockHttpSession; @@ -59,6 +63,7 @@ import no.priv.bang.ukelonn.backend.UkelonnServiceProvider; import no.priv.bang.ukelonn.beans.Account; import no.priv.bang.ukelonn.beans.AccountWithJobIds; +import no.priv.bang.ukelonn.beans.Bonus; import no.priv.bang.ukelonn.beans.Notification; import no.priv.bang.ukelonn.beans.PerformedTransaction; import no.priv.bang.ukelonn.beans.SumYear; @@ -77,6 +82,8 @@ * */ public class UkelonnRestApiServletTest extends ServletTestBase { + public static final ObjectMapper mapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @Test public void testLoginOk() throws Exception { @@ -1948,6 +1955,142 @@ public void testNotifications() throws Exception { assertEquals(utbetalt.getMessage(), notificationsToJad2.get(0).getMessage()); } + @Test + public void testGetActiveBonuses() throws Exception { + // Set up REST API servlet with mocked services + UkelonnService ukelonn = mock(UkelonnService.class); + when(ukelonn.getActiveBonuses()).thenReturn(Collections.singletonList(new Bonus())); + + MockLogService logservice = new MockLogService(); + UserManagementService useradmin = mock(UserManagementService.class); + + UkelonnRestApiServlet servlet = simulateDSComponentActivationAndWebWhiteboardConfiguration(ukelonn, logservice, useradmin); + + // Create the request and response + MockHttpServletRequest request = buildGetUrl("/activebonuses"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + // Run the method under test + servlet.service(request, response); + + // Check the response + assertEquals(200, response.getStatus()); + assertEquals("application/json", response.getContentType()); + List activeBonuses = mapper.readValue(getBinaryContent(response), new TypeReference>() {}); + assertThat(activeBonuses).isNotEmpty(); + } + + @Test + public void testGetAllBonuses() throws Exception { + // Set up REST API servlet with mocked services + UkelonnService ukelonn = mock(UkelonnService.class); + when(ukelonn.getAllBonuses()).thenReturn(Collections.singletonList(new Bonus())); + + MockLogService logservice = new MockLogService(); + UserManagementService useradmin = mock(UserManagementService.class); + + UkelonnRestApiServlet servlet = simulateDSComponentActivationAndWebWhiteboardConfiguration(ukelonn, logservice, useradmin); + + // Create the request and response + MockHttpServletRequest request = buildGetUrl("/allbonuses"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + // Run the method under test + servlet.service(request, response); + + // Check the response + assertEquals(200, response.getStatus()); + assertEquals("application/json", response.getContentType()); + List allBonuses = mapper.readValue(getBinaryContent(response), new TypeReference>() {}); + assertThat(allBonuses).isNotEmpty(); + } + + @Test + public void testPostCreateBonus() throws Exception { + // Set up REST API servlet with mocked services + Bonus bonus = new Bonus(1, true, null, "Julebonus", "Dobbelt lønn for jobb", 2.0, new Date(), new Date()); + UkelonnService ukelonn = mock(UkelonnService.class); + when(ukelonn.createBonus(eq(bonus))).thenReturn(Collections.singletonList(bonus)); + + MockLogService logservice = new MockLogService(); + UserManagementService useradmin = mock(UserManagementService.class); + + UkelonnRestApiServlet servlet = simulateDSComponentActivationAndWebWhiteboardConfiguration(ukelonn, logservice, useradmin); + + // Create the request and response + MockHttpServletRequest request = buildPostUrl("/admin/createbonus"); + String postBody = mapper.writeValueAsString(bonus); + request.setBodyContent(postBody); + MockHttpServletResponse response = new MockHttpServletResponse(); + + // Run the method under test + servlet.service(request, response); + + // Check the response + assertEquals(200, response.getStatus()); + assertEquals("application/json", response.getContentType()); + List bonusesWithAddedBonus = mapper.readValue(getBinaryContent(response), new TypeReference>() {}); + assertThat(bonusesWithAddedBonus).contains(bonus); + } + + @Test + public void testPostUpdateBonus() throws Exception { + // Set up REST API servlet with mocked services + Bonus bonus = new Bonus(1, true, null, "Julebonus", "Dobbelt lønn for jobb", 2.0, new Date(), new Date()); + UkelonnService ukelonn = mock(UkelonnService.class); + when(ukelonn.modifyBonus(eq(bonus))).thenReturn(Collections.singletonList(bonus)); + + MockLogService logservice = new MockLogService(); + UserManagementService useradmin = mock(UserManagementService.class); + + UkelonnRestApiServlet servlet = simulateDSComponentActivationAndWebWhiteboardConfiguration(ukelonn, logservice, useradmin); + + // Create the request and response + MockHttpServletRequest request = buildPostUrl("/admin/modifybonus"); + String postBody = mapper.writeValueAsString(bonus); + request.setBodyContent(postBody); + MockHttpServletResponse response = new MockHttpServletResponse(); + + // Run the method under test + servlet.service(request, response); + + // Check the response + assertEquals(200, response.getStatus()); + assertEquals("application/json", response.getContentType()); + List bonusesWithUpdatedBonus = mapper.readValue(getBinaryContent(response), new TypeReference>() {}); + assertThat(bonusesWithUpdatedBonus).contains(bonus); + } + + @Test + public void testPostDeleteBonus() throws Exception { + // Set up REST API servlet with mocked services + Bonus bonus = new Bonus(1, true, null, "Julebonus", "Dobbelt lønn for jobb", 2.0, new Date(), new Date()); + UkelonnService ukelonn = mock(UkelonnService.class); + when(ukelonn.deleteBonus(eq(bonus))).thenReturn(Collections.singletonList(new Bonus())); + + MockLogService logservice = new MockLogService(); + UserManagementService useradmin = mock(UserManagementService.class); + + UkelonnRestApiServlet servlet = simulateDSComponentActivationAndWebWhiteboardConfiguration(ukelonn, logservice, useradmin); + + // Create the request and response + MockHttpServletRequest request = buildPostUrl("/admin/deletebonus"); + String postBody = mapper.writeValueAsString(bonus); + request.setBodyContent(postBody); + MockHttpServletResponse response = new MockHttpServletResponse(); + + // Run the method under test + servlet.service(request, response); + + // Check the response + assertEquals(200, response.getStatus()); + assertEquals("application/json", response.getContentType()); + List bonusesWithDeletedBonus = mapper.readValue(getBinaryContent(response), new TypeReference>() {}); + assertThat(bonusesWithDeletedBonus) + .isNotEmpty() + .doesNotContain(bonus); + } + private byte[] getBinaryContent(MockHttpServletResponse response) throws IOException { MockServletOutputStream outputstream = (MockServletOutputStream) response.getOutputStream(); return outputstream.getBinaryContent(); @@ -1968,24 +2111,44 @@ private TransactionType findJobTypeWithDifferentIdAndAmount(Integer transactionT return getJobtypes().stream().filter(t->!t.getId().equals(transactionTypeId)).filter(t->t.getTransactionAmount() != amount).collect(Collectors.toList()).get(0); } - private MockHttpServletRequest buildGetUrl(String localpath) { - MockHttpServletRequest request = buildGetRootUrl(); - request.setRequestURL("http://localhost:8181/ukelonn/api" + localpath); - request.setRequestURI("/ukelonn/api" + localpath); + + private MockHttpServletRequest buildGetUrl(String resource) { + MockHttpServletRequest request = buildRequest(resource); + request.setMethod("GET"); return request; } - private MockHttpServletRequest buildGetRootUrl() { + private MockHttpServletRequest buildPostUrl(String resource) throws Exception { + String contenttype = MediaType.APPLICATION_JSON; + MockHttpServletRequest request = buildRequest(resource); + request.setMethod("POST"); + request.setContentType(contenttype); + request.addHeader("Content-Type", contenttype); + request.setCharacterEncoding("UTF-8"); + return request; + } + + private MockHttpServletRequest buildRequest(String resource) { MockHttpSession session = new MockHttpSession(); MockHttpServletRequest request = new MockHttpServletRequest(); request.setProtocol("HTTP/1.1"); - request.setMethod("GET"); - request.setRequestURL("http://localhost:8181/ukelonn/api/"); - request.setRequestURI("/ukelonn/api/"); + request.setRequestURL("http://localhost:8181/ukelon/api" + resource); + request.setRequestURI("/ukelonn/api" + resource); request.setContextPath("/ukelonn"); request.setServletPath("/api"); request.setSession(session); return request; } + private UkelonnRestApiServlet simulateDSComponentActivationAndWebWhiteboardConfiguration(UkelonnService ukelonn, LogService logservice, UserManagementService useradmin) throws Exception { + UkelonnRestApiServlet servlet = new UkelonnRestApiServlet(); + servlet.setLogservice(logservice); + servlet.setUkelonnService(ukelonn); + servlet.setUserManagement(useradmin); + servlet.activate(); + ServletConfig config = createServletConfigWithApplicationAndPackagenameForJerseyResources(); + servlet.init(config); + return servlet; + } + } diff --git a/ukelonn.web.services/src/test/java/no/priv/bang/ukelonn/api/resources/BonusesTest.java b/ukelonn.web.services/src/test/java/no/priv/bang/ukelonn/api/resources/BonusesTest.java new file mode 100644 index 00000000..2b591eb5 --- /dev/null +++ b/ukelonn.web.services/src/test/java/no/priv/bang/ukelonn/api/resources/BonusesTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Steinar Bang + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations + * under the License. + */ +package no.priv.bang.ukelonn.api.resources; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.junit.Test; + +import no.priv.bang.ukelonn.UkelonnService; +import no.priv.bang.ukelonn.beans.Bonus; + +public class BonusesTest { + + @Test + public void testGetActiveBonuses() { + UkelonnService ukelonn = mock(UkelonnService.class); + when(ukelonn.getActiveBonuses()).thenReturn(Collections.singletonList(new Bonus())); + + Bonuses resource = new Bonuses(); + resource.ukelonn = ukelonn; + + List activeBonuses = resource.getActiveBonuses(); + assertThat(activeBonuses).isNotEmpty(); + } + + @Test + public void testGetAllBonuses() { + UkelonnService ukelonn = mock(UkelonnService.class); + when(ukelonn.getAllBonuses()).thenReturn(Collections.singletonList(new Bonus())); + + Bonuses resource = new Bonuses(); + resource.ukelonn = ukelonn; + + List activeBonuses = resource.getAllBonuses(); + assertThat(activeBonuses).isNotEmpty(); + } + + @Test + public void testCreateBonus() { + Bonus bonus = new Bonus(1, true, null, "Julebonus", "Dobbelt lønn for jobb", 2.0, new Date(), new Date()); + UkelonnService ukelonn = mock(UkelonnService.class); + when(ukelonn.createBonus(any())).thenReturn(Collections.singletonList(bonus)); + + Bonuses resource = new Bonuses(); + resource.ukelonn = ukelonn; + + List bonuses = resource.createBonus(bonus); + assertEquals(bonus, bonuses.get(0)); + } + + @Test + public void testModifyBonus() { + Bonus bonus = new Bonus(1, true, null, "Julebonus", "Dobbelt lønn for jobb", 2.0, new Date(), new Date()); + UkelonnService ukelonn = mock(UkelonnService.class); + when(ukelonn.modifyBonus(any())).thenReturn(Collections.singletonList(bonus)); + + Bonuses resource = new Bonuses(); + resource.ukelonn = ukelonn; + + List bonuses = resource.modifyBonus(bonus); + assertEquals(bonus, bonuses.get(0)); + } + + @Test + public void testDeleteBonus() { + Bonus bonus = new Bonus(1, true, null, "Julebonus", "Dobbelt lønn for jobb", 2.0, new Date(), new Date()); + UkelonnService ukelonn = mock(UkelonnService.class); + when(ukelonn.deleteBonus(any())).thenReturn(Collections.singletonList(new Bonus())); + + Bonuses resource = new Bonuses(); + resource.ukelonn = ukelonn; + + List bonuses = resource.deleteBonus(bonus); + assertThat(bonuses).isNotEmpty().doesNotContain(bonus); + } + +}