górne menu - wiadomości
Deploy Polska Lokalnie / deploy (push) Successful in 22s

This commit is contained in:
2026-09-25 10:09:33 +02:00
parent 517e80d6bd
commit cf02f480ed
22 changed files with 577 additions and 154 deletions
@@ -3,8 +3,6 @@ package pl.polskalokalnie.auth;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Locale;
@@ -112,7 +110,10 @@ public class AuthService {
String phone = request.phone() != null && !request.phone().isBlank() ? request.phone().trim() : null;
user.setPhone(phone);
user.setNip(request.nip() != null && !request.nip().isBlank() ? request.nip().trim() : null);
user.setAddress(request.address() != null && !request.address().isBlank() ? request.address().trim() : null);
// Adres zapisujemy tylko firmom - konto prywatne go nie potrzebuje (minimalizacja danych).
if (user.getAccountType() == AccountType.COMPANY) {
user.setAddress(request.address() != null && !request.address().isBlank() ? request.address().trim() : null);
}
// Rejestracja jest mozliwa tylko po zaznaczeniu zgody w formularzu - zapisujemy jej moment.
if (Boolean.TRUE.equals(request.acceptTerms())) {
user.setTermsAcceptedAt(Instant.now());
@@ -307,7 +308,7 @@ public class AuthService {
AppUser user = userRepository.findByEmailIgnoreCase(email)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Sesja wygasła"));
textModerationService.validateOrThrow(request.fullName(), request.nick(), request.phone(), request.birthDate(), request.address());
textModerationService.validateOrThrow(request.fullName(), request.nick(), request.phone(), request.address());
user.setFullName(request.fullName().trim());
if (request.nick() != null && !request.nick().isBlank()) {
@@ -321,22 +322,20 @@ public class AuthService {
user.setPhoneVerified(false);
}
user.setPhone(phone);
user.setAddress(request.address() != null && !request.address().isBlank() ? request.address().trim() : null);
// Adres przyjmujemy wylacznie od kont firmowych (siedziba przedsiebiorcy). Konto prywatne
// nie ma po co podawac adresu zamieszkania, wiec nawet przyslany z zewnatrz zostaje odrzucony
// i kasuje to, co ewentualnie zapisano wczesniej.
if (user.getAccountType() == AccountType.COMPANY) {
user.setAddress(request.address() != null && !request.address().isBlank() ? request.address().trim() : null);
} else {
user.setAddress(null);
}
// NIP: null = nie zmieniaj (zapis czesciowy nie moze skasowac NIP-u firmy), pusty = wyczysc.
if (request.nip() != null) {
user.setNip(request.nip().isBlank() ? null : request.nip().trim());
}
user.setContactPreference(request.contactPreference() != null ? request.contactPreference() : ContactPreference.EMAIL_AND_PHONE);
user.setPreferredLanguage(request.preferredLanguage() != null ? request.preferredLanguage() : PreferredLanguage.PL);
if (request.birthDate() != null && !request.birthDate().isBlank()) {
try {
user.setBirthDate(LocalDate.parse(request.birthDate().trim()));
} catch (DateTimeParseException ex) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Nieprawidłowy format daty urodzenia");
}
} else {
user.setBirthDate(null);
}
AppUser saved = userRepository.save(user);
leadSyncService.syncUser(saved);
@@ -10,7 +10,7 @@ public record UpdateProfileRequest(
// Pusty nick oznacza "nie zmieniaj" - dotychczasowa nazwa uzytkownika zostaje.
String nick,
String phone,
String birthDate,
// Adres siedziby - przyjmowany tylko od kont firmowych.
String address,
// NIP firmy. null = nie zmieniaj (zapisy czesciowe, np. jezyka, nie kasuja NIP-u);
// pusty ciag = wyczysc. Dotyczy kont firmowych.
@@ -1,7 +1,6 @@
package pl.polskalokalnie.auth.dto;
import java.time.Instant;
import java.time.LocalDate;
import pl.polskalokalnie.user.AccountType;
import pl.polskalokalnie.user.AppUser;
import pl.polskalokalnie.user.AuthProvider;
@@ -22,7 +21,6 @@ public record UserResponse(
ContactPreference contactPreference,
PreferredLanguage preferredLanguage,
String nip,
LocalDate birthDate,
boolean verified,
boolean phoneVerified,
boolean blocked,
@@ -50,7 +48,6 @@ public record UserResponse(
user.getContactPreference() != null ? user.getContactPreference() : ContactPreference.EMAIL_AND_PHONE,
user.getPreferredLanguage() != null ? user.getPreferredLanguage() : PreferredLanguage.PL,
user.getNip(),
user.getBirthDate(),
user.isVerified(),
user.isPhoneVerified(),
user.isBlocked(),
@@ -13,6 +13,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import pl.polskalokalnie.moderation.TextModerationService;
import pl.polskalokalnie.notification.NotificationService;
import pl.polskalokalnie.search.SavedSearchNotifier;
import pl.polskalokalnie.user.AccountType;
import pl.polskalokalnie.user.AppUser;
import pl.polskalokalnie.user.UserRepository;
@@ -28,15 +29,17 @@ public class ListingService {
private final NotificationService notificationService;
private final UserRepository userRepository;
private final UserSettingsService userSettingsService;
private final SavedSearchNotifier savedSearchNotifier;
public ListingService(ListingRepository listingRepository, TextModerationService textModerationService,
NotificationService notificationService, UserRepository userRepository,
UserSettingsService userSettingsService) {
UserSettingsService userSettingsService, SavedSearchNotifier savedSearchNotifier) {
this.listingRepository = listingRepository;
this.textModerationService = textModerationService;
this.notificationService = notificationService;
this.userRepository = userRepository;
this.userSettingsService = userSettingsService;
this.savedSearchNotifier = savedSearchNotifier;
}
// Konto wlasciciela ogloszenia - null, gdy ogloszenie nie ma odpowiednika wsrod uzytkownikow.
@@ -183,7 +186,11 @@ public class ListingService {
throw new ResponseStatusException(HttpStatus.CONFLICT, "Tylko opublikowane ogloszenie mozna wstrzymac lub wznowic");
}
listing.setStatus(status);
return ListingResponse.from(listingRepository.save(listing));
PropertyListing saved = listingRepository.save(listing);
if (status == ListingStatus.APPROVED) {
savedSearchNotifier.notifyMatching(saved);
}
return ListingResponse.from(saved);
}
@Transactional
@@ -309,6 +316,11 @@ public class ListingService {
listing.setStatus(status);
PropertyListing saved = listingRepository.save(listing);
notifyOwnerStatusChange(saved, status);
if (status == ListingStatus.APPROVED) {
// Ogloszenie wlasnie stalo sie widoczne - szukajacy z pasujacym zapisanym
// wyszukiwaniem dostaja powiadomienie na liscie w serwisie.
savedSearchNotifier.notifyMatching(saved);
}
return ListingResponse.from(saved);
}
@@ -181,10 +181,7 @@ public class MeetingService {
}
private void sendConfirmationEmail(AppUser recipient, AppUser other, Meeting meeting) {
boolean wantsEmail = userSettingsRepository.findByUserId(recipient.getId())
.map(UserSettings::isNotifyMessages)
.orElse(true);
if (!wantsEmail) {
if (!wantsNotifications(recipient)) {
return;
}
accountEmailService.sendMeetingConfirmed(recipient.getEmail(), recipient.getFullName(), displayName(other),
@@ -192,10 +189,21 @@ public class MeetingService {
}
private void notify(AppUser recipient, String title, String body, Long conversationId) {
// Terminy oglądania ustala się w rozmowie, więc podlegają temu samemu przełącznikowi
// co wiadomości. Sam termin zostaje w kalendarzu na /konto/spotkania.
if (!wantsNotifications(recipient)) {
return;
}
notificationService.create(recipient.getEmail(), "messages", "calendar", title, body,
conversationId == null ? "meetings" : "messages:conversation:" + conversationId, null);
}
private boolean wantsNotifications(AppUser recipient) {
return userSettingsRepository.findByUserId(recipient.getId())
.map(UserSettings::isNotifyMessages)
.orElse(true);
}
private String when(Meeting meeting) {
return meeting.getMeetingDate().format(DATE) + ", " + meeting.getStartTime().format(TIME)
+ "-" + meeting.getEndTime().format(TIME);
@@ -48,19 +48,23 @@ public class ConversationMessageNotifier {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onMessageSent(MessageSentEvent event) {
try {
// Przelacznik "Wiadomości od użytkowników" w ustawieniach konta wycisza oba kanaly:
// powiadomienie w serwisie i e-mail. Sama wiadomosc i tak czeka w /wiadomosci.
boolean wantsNotification = userSettingsRepository.findByUserId(event.recipientId())
.map(UserSettings::isNotifyMessages)
.orElse(true);
if (!wantsNotification) {
return;
}
String preview = event.content().length() > 90 ? event.content().substring(0, 90) + "…" : event.content();
notificationService.create(event.recipientEmail(), "messages", "message",
"Nowa wiadomość: " + event.listingTitle(),
event.senderName() + ": " + preview,
"messages:conversation:" + event.conversationId(), null);
boolean wantsEmail = userSettingsRepository.findByUserId(event.recipientId())
.map(UserSettings::isNotifyMessages)
.orElse(true);
if (wantsEmail) {
accountEmailService.sendNewMessage(event.recipientEmail(), event.recipientName(), event.senderName(),
event.listingTitle(), event.content(), event.conversationId());
}
accountEmailService.sendNewMessage(event.recipientEmail(), event.recipientName(), event.senderName(),
event.listingTitle(), event.content(), event.conversationId());
} catch (RuntimeException ex) {
log.warn("Nie udalo sie powiadomic o wiadomosci w rozmowie {}: {}", event.conversationId(), ex.getMessage());
}
@@ -20,6 +20,9 @@ import pl.polskalokalnie.pricealert.PriceAlertDtos.PriceAlertEventResponse;
import pl.polskalokalnie.pricealert.PriceAlertDtos.PriceAlertRequest;
import pl.polskalokalnie.pricealert.PriceAlertDtos.PriceAlertResponse;
import pl.polskalokalnie.pricehistory.PriceSnapshotService;
import pl.polskalokalnie.user.UserRepository;
import pl.polskalokalnie.user.UserSettings;
import pl.polskalokalnie.user.UserSettingsRepository;
/**
* Alerty cenowe: obserwowane ogloszenia i srednie ceny za m2 w miastach.
@@ -45,17 +48,35 @@ public class PriceAlertService {
private final ListingRepository listingRepository;
private final PriceSnapshotService snapshotService;
private final NotificationService notificationService;
private final UserRepository userRepository;
private final UserSettingsRepository userSettingsRepository;
public PriceAlertService(PriceAlertRepository alertRepository,
PriceAlertEventRepository eventRepository,
ListingRepository listingRepository,
PriceSnapshotService snapshotService,
NotificationService notificationService) {
NotificationService notificationService,
UserRepository userRepository,
UserSettingsRepository userSettingsRepository) {
this.alertRepository = alertRepository;
this.eventRepository = eventRepository;
this.listingRepository = listingRepository;
this.snapshotService = snapshotService;
this.notificationService = notificationService;
this.userRepository = userRepository;
this.userSettingsRepository = userSettingsRepository;
}
/**
* Globalny przelacznik "Alerty cenowe" z ustawien konta. Wylaczony wycisza wszystkie alerty
* naraz; przelacznik przy pojedynczym alercie dziala niezaleznie i wycisza tylko jego.
* Historia zmian zapisuje sie tak czy inaczej - wyciszamy powiadomienie, nie pomiar.
*/
private boolean wantsAlertNotifications(String userEmail) {
return userRepository.findByEmailIgnoreCase(userEmail)
.flatMap(user -> userSettingsRepository.findByUserId(user.getId()))
.map(UserSettings::isNotifyPriceAlerts)
.orElse(true);
}
@Transactional(readOnly = true)
@@ -147,7 +168,7 @@ public class PriceAlertService {
event.setCurrentValue(current);
eventRepository.save(event);
if (alert.isNotificationsEnabled()) {
if (alert.isNotificationsEnabled() && wantsAlertNotifications(alert.getUserEmail())) {
notify(alert, previous, current, changePct);
}
return true;
@@ -0,0 +1,170 @@
package pl.polskalokalnie.search;
import java.math.BigDecimal;
import java.util.List;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import pl.polskalokalnie.listing.OfferType;
import pl.polskalokalnie.listing.PropertyListing;
import pl.polskalokalnie.listing.PropertyType;
import pl.polskalokalnie.notification.NotificationService;
import pl.polskalokalnie.user.UserRepository;
import pl.polskalokalnie.user.UserSettings;
import pl.polskalokalnie.user.UserSettingsRepository;
/**
* Powiadomienia o nowych ofertach pasujacych do zapisanych wyszukiwan.
*
* Trafiaja wylacznie na liste powiadomien w serwisie (/powiadomienia) - maili z nich nie
* wysylamy. Wywolywane, gdy ogloszenie staje sie widoczne publicznie (moderacja zatwierdza
* albo wlasciciel wznawia wstrzymane ogloszenie). Klucz deduplikacji pilnuje, zeby to samo
* ogloszenie nie wrocilo do tego samego wyszukiwania po kolejnej zmianie statusu.
*/
@Service
public class SavedSearchNotifier {
private static final Logger log = LoggerFactory.getLogger(SavedSearchNotifier.class);
private final SavedSearchRepository savedSearchRepository;
private final UserSettingsRepository userSettingsRepository;
private final UserRepository userRepository;
private final NotificationService notificationService;
public SavedSearchNotifier(SavedSearchRepository savedSearchRepository,
UserSettingsRepository userSettingsRepository, UserRepository userRepository,
NotificationService notificationService) {
this.savedSearchRepository = savedSearchRepository;
this.userSettingsRepository = userSettingsRepository;
this.userRepository = userRepository;
this.notificationService = notificationService;
}
public void notifyMatching(PropertyListing listing) {
if (listing == null || listing.getId() == null) {
return;
}
List<SavedSearch> searches;
try {
searches = savedSearchRepository.findAll();
} catch (RuntimeException ex) {
log.warn("Nie udalo sie odczytac zapisanych wyszukiwan: {}", ex.getMessage());
return;
}
for (SavedSearch search : searches) {
String email = search.getUserEmail();
if (email == null || email.equalsIgnoreCase(listing.getOwnerEmail())) {
// Wlasciciel nie dostaje powiadomienia o wlasnym ogloszeniu.
continue;
}
if (!matches(search, listing) || !wantsNotification(email)) {
continue;
}
notificationService.create(email, "listing", "search",
"Nowa oferta z zapisanego wyszukiwania",
listing.getTitle() + " - " + listing.getCity() + ", " + price(listing),
"listing:" + listing.getId(),
"saved-search:" + search.getId() + ":" + listing.getId());
}
}
private boolean wantsNotification(String email) {
return userRepository.findByEmailIgnoreCase(email)
.flatMap(user -> userSettingsRepository.findByUserId(user.getId()))
.map(UserSettings::isNotifySavedSearches)
.orElse(true);
}
/**
* Te same kryteria, ktore filtruja liste ofert. Puste pole znaczy "bez ograniczenia",
* wiec brak wartosci nigdy nie odrzuca ogloszenia.
*/
boolean matches(SavedSearch search, PropertyListing listing) {
OfferType wanted = "rent".equalsIgnoreCase(search.getMode()) ? OfferType.RENT : OfferType.SALE;
if (listing.getOfferType() != wanted) {
return false;
}
if (notBlank(search.getCity()) && !search.getCity().trim().equalsIgnoreCase(listing.getCity())) {
return false;
}
PropertyType type = propertyType(search.getPropertyType());
if (type != null && listing.getPropertyType() != type) {
return false;
}
// Zapisane wyszukiwanie moze dotyczyc rodzaju nieruchomosci, ktorego serwis jeszcze
// nie obsluguje (np. dzialka) - wtedy zadne ogloszenie do niego nie pasuje.
if (type == null && notBlank(search.getPropertyType()) && !isAnyPropertyType(search.getPropertyType())) {
return false;
}
BigDecimal price = listing.getPrice();
if (price != null) {
if (search.getPriceMin() != null && price.compareTo(BigDecimal.valueOf(search.getPriceMin())) < 0) {
return false;
}
if (search.getPriceMax() != null && price.compareTo(BigDecimal.valueOf(search.getPriceMax())) > 0) {
return false;
}
}
Double area = listing.getArea();
if (area != null) {
if (search.getAreaMin() != null && area < search.getAreaMin()) {
return false;
}
if (search.getAreaMax() != null && area > search.getAreaMax()) {
return false;
}
}
if (search.getRooms() != null && listing.getRooms() != null && listing.getRooms() < search.getRooms()) {
return false;
}
return marketMatches(search, listing);
}
/** Zaznaczenie obu rynkow (albo zadnego) znaczy "bez znaczenia". */
private boolean marketMatches(SavedSearch search, PropertyListing listing) {
boolean secondary = search.isSecondaryMarket();
boolean primary = search.isPrimaryMarket();
if (secondary == primary) {
return true;
}
String market = listing.getMarket();
if (market == null || market.isBlank()) {
return true;
}
String normalized = market.trim().toLowerCase(Locale.ROOT);
return secondary ? normalized.startsWith("wt") : normalized.startsWith("pierw");
}
private PropertyType propertyType(String label) {
if (label == null) {
return null;
}
String normalized = label.trim().toLowerCase(Locale.ROOT);
if (normalized.startsWith("mieszkan")) {
return PropertyType.APARTMENT;
}
if (normalized.startsWith("dom")) {
return PropertyType.HOUSE;
}
return null;
}
private boolean isAnyPropertyType(String label) {
String normalized = label.trim().toLowerCase(Locale.ROOT);
return normalized.startsWith("dowoln") || normalized.startsWith("wszystk");
}
private boolean notBlank(String value) {
return value != null && !value.isBlank();
}
private String price(PropertyListing listing) {
BigDecimal price = listing.getPrice();
if (price == null) {
return "cena do uzgodnienia";
}
return String.format(Locale.ROOT, "%,d zł", price.longValue()).replace(',', ' ');
}
}
@@ -10,7 +10,6 @@ import jakarta.persistence.Id;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Table;
import java.time.Instant;
import java.time.LocalDate;
import org.hibernate.annotations.ColumnDefault;
@Entity
@@ -52,6 +51,9 @@ public class AppUser {
@Column(length = 30)
private String phone;
// Adres dotyczy wylacznie kont firmowych (siedziba przedsiebiorcy - dana jawna z CEIDG/KRS).
// Konto prywatne go nie ma: do kontaktu wystarcza e-mail i telefon, a adres nieruchomosci
// stoi przy ogloszeniu. Minimalizacja danych, art. 5 ust. 1 lit. c RODO.
@Column(length = 255)
private String address;
@@ -68,8 +70,6 @@ public class AppUser {
@Column(length = 20)
private String nip;
private LocalDate birthDate;
// Saldo kredytow promowania (uniwersalny token: 1 kredyt = 1 promowanie dowolnego planu).
@Column(nullable = false)
@ColumnDefault("0")
@@ -220,14 +220,6 @@ public class AppUser {
this.nip = nip;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public int getPromotionCredits() {
return promotionCredits;
}
@@ -110,10 +110,6 @@ public class UserSettings {
@ColumnDefault("true")
private boolean notifyMessages = true;
@Column(nullable = false)
@ColumnDefault("false")
private boolean notifyProductNews = false;
/**
* Lokalizacje ostatnio sprawdzone w Historii cen (JSON, maks. kilka pozycji). Lista jest
* na koncie, a nie w przegladarce, wiec porownanie cen wyglada tak samo na kazdym urzadzeniu.
@@ -310,14 +306,6 @@ public class UserSettings {
this.notifyMessages = notifyMessages;
}
public boolean isNotifyProductNews() {
return notifyProductNews;
}
public void setNotifyProductNews(boolean notifyProductNews) {
this.notifyProductNews = notifyProductNews;
}
public String getSearchLocations() {
return searchLocations;
}
@@ -24,7 +24,6 @@ public record UserSettingsRequest(
Boolean notifySavedSearches,
Boolean notifyPriceAlerts,
Boolean notifyMessages,
Boolean notifyProductNews,
String priceHistoryMarkets,
String searchLocations,
String searchPropertyType,
@@ -21,7 +21,6 @@ public record UserSettingsResponse(
boolean notifySavedSearches,
boolean notifyPriceAlerts,
boolean notifyMessages,
boolean notifyProductNews,
String priceHistoryMarkets,
String searchLocations,
String searchPropertyType,
@@ -52,7 +51,6 @@ public record UserSettingsResponse(
settings.isNotifySavedSearches(),
settings.isNotifyPriceAlerts(),
settings.isNotifyMessages(),
settings.isNotifyProductNews(),
settings.getPriceHistoryMarkets(),
settings.getSearchLocations(),
settings.getSearchPropertyType(),
@@ -132,9 +132,6 @@ public class UserSettingsService {
if (request.notifyMessages() != null) {
settings.setNotifyMessages(request.notifyMessages());
}
if (request.notifyProductNews() != null) {
settings.setNotifyProductNews(request.notifyProductNews());
}
if (request.priceHistoryMarkets() != null) {
String markets = request.priceHistoryMarkets().trim();
settings.setPriceHistoryMarkets(markets.isEmpty() ? null : markets);
@@ -12,6 +12,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import pl.polskalokalnie.moderation.TextModerationService;
import pl.polskalokalnie.notification.NotificationService;
import pl.polskalokalnie.search.SavedSearchNotifier;
import pl.polskalokalnie.user.UserRepository;
import pl.polskalokalnie.user.UserSettingsService;
@@ -22,7 +23,8 @@ class ListingEditModerationTest {
private final ListingRepository repository = mock(ListingRepository.class);
private final ListingService service = new ListingService(repository, mock(TextModerationService.class),
mock(NotificationService.class), mock(UserRepository.class), mock(UserSettingsService.class));
mock(NotificationService.class), mock(UserRepository.class), mock(UserSettingsService.class),
mock(SavedSearchNotifier.class));
private PropertyListing listing;
private ListingCreateRequest request(String title, BigDecimal price, String phone) {
@@ -0,0 +1,120 @@
package pl.polskalokalnie.search;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
import pl.polskalokalnie.listing.OfferType;
import pl.polskalokalnie.listing.PropertyListing;
import pl.polskalokalnie.listing.PropertyType;
import pl.polskalokalnie.notification.NotificationService;
import pl.polskalokalnie.user.UserRepository;
import pl.polskalokalnie.user.UserSettingsRepository;
/**
* Dopasowanie nowego ogloszenia do zapisanego wyszukiwania. Puste kryterium znaczy
* "bez ograniczenia" - nie moze odrzucac ofert.
*/
class SavedSearchNotifierTest {
private final SavedSearchNotifier notifier = new SavedSearchNotifier(
mock(SavedSearchRepository.class), mock(UserSettingsRepository.class),
mock(UserRepository.class), mock(NotificationService.class));
private PropertyListing listing() {
PropertyListing listing = new PropertyListing();
listing.setTitle("Mieszkanie 3 pokoje");
listing.setOfferType(OfferType.SALE);
listing.setPropertyType(PropertyType.APARTMENT);
listing.setCity("Gdańsk");
listing.setPrice(BigDecimal.valueOf(750_000));
listing.setArea(62.0);
listing.setRooms(3);
listing.setMarket("Wtórny");
return listing;
}
private SavedSearch search() {
SavedSearch search = new SavedSearch();
search.setMode("buy");
return search;
}
@Test
void pusteWyszukiwaniePasujeDoKazdejOfertySprzedazy() {
assertThat(notifier.matches(search(), listing())).isTrue();
}
@Test
void wyszukiwanieWynajmuNiePasujeDoSprzedazy() {
SavedSearch search = search();
search.setMode("rent");
assertThat(notifier.matches(search, listing())).isFalse();
}
@Test
void miastoMusiSieZgadzac() {
SavedSearch search = search();
search.setCity("Gdańsk");
assertThat(notifier.matches(search, listing())).isTrue();
search.setCity("Kraków");
assertThat(notifier.matches(search, listing())).isFalse();
}
@Test
void cenaPozaWidelkamiOdrzucaOferte() {
SavedSearch search = search();
search.setPriceMax(700_000L);
assertThat(notifier.matches(search, listing())).isFalse();
search.setPriceMax(800_000L);
assertThat(notifier.matches(search, listing())).isTrue();
search.setPriceMin(900_000L);
assertThat(notifier.matches(search, listing())).isFalse();
}
@Test
void metrazIPokojeDzialajaJakoWartosciMinimalne() {
SavedSearch search = search();
search.setAreaMin(70);
assertThat(notifier.matches(search, listing())).isFalse();
search.setAreaMin(50);
search.setRooms(4);
assertThat(notifier.matches(search, listing())).isFalse();
search.setRooms(2);
assertThat(notifier.matches(search, listing())).isTrue();
}
@Test
void rynekOgraniczaTylkoWtedyGdyWybranoJeden() {
SavedSearch search = search();
search.setSecondaryMarket(true);
search.setPrimaryMarket(true);
assertThat(notifier.matches(search, listing())).isTrue();
search.setSecondaryMarket(false);
assertThat(notifier.matches(search, listing())).isFalse();
search.setSecondaryMarket(true);
search.setPrimaryMarket(false);
assertThat(notifier.matches(search, listing())).isTrue();
}
@Test
void rodzajNieruchomosciSpozaOfertySerwisuNigdyNiePasuje() {
SavedSearch search = search();
search.setPropertyType("Działka");
assertThat(notifier.matches(search, listing())).isFalse();
search.setPropertyType("Mieszkanie");
assertThat(notifier.matches(search, listing())).isTrue();
search.setPropertyType("Dom");
assertThat(notifier.matches(search, listing())).isFalse();
}
}