Adnotacja @Transactional w testach

Adnotacja @Transactional w testach
SC
  • Rejestracja:około rok
  • Ostatnio:około 9 godzin
  • Postów:19
0

Czy w testach powinno używać się adnotacji @Transactional? Musiałem ją dodać w tym teście, bo inaczej w metodzie findEventToEdit, konkretnie w linii if (!currentUser.equals(event.getOrganizer())) { leciał LazyInitializationException. Encje Event i AppUser są w relacji jednokierunkowej OneToOne. Teraz odkąd zacząłem używać w testach 2 plików sql - pierwszy do dodawania danych testowych przed każdym testem, drugi do czyszczenia tabel po każdym teście to mam nowy problem - metoda getReferenceById zwraca mi obiekt AppUser$HibernateProxy... zamiast AppUser, w którym wszystkie pola są null. Usunięcie adnotacji @Transactional z testu pomaga, ale wtedy leci wcześniej wspomniany wyjątek. W czym jest problem?

Kopiuj
@Test
@Transactional
void shouldReturnEventToEdit() {
    // when
    var eventToEdit = organizerEventService.findEventToEdit(appUserRepository.getReferenceById(2L), 5L);
    // then
    assertThat(eventToEdit.getName()).isEqualTo("Java Dev Talks #5");
    assertThat(eventToEdit.getEventImageData()).isNotBlank();
    assertThat(eventToEdit.getEventImage()).isNull();
    assertThat(eventToEdit.getEventType()).isEqualTo(MEETING);
    assertThat(eventToEdit.getDateTime()).isEqualTo(TOMORROW.plusMonths(1).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    assertThat(eventToEdit.getLanguage()).isEqualTo("polski");
    assertThat(eventToEdit.getAdmission()).isEqualTo(FREE);
    assertThat(eventToEdit.getCity()).isEqualTo("Rzeszów");
    assertThat(eventToEdit.getLocation()).isEqualTo("WSIiZ");
    assertThat(eventToEdit.getAddress()).isEqualTo("Sucharskiego 2, 35-225 Rzeszów");
    assertThat(eventToEdit.getDescription()).isEqualTo("Spotkanie rzeszowskiej grupy pasjonatów języka Java.");
}
Kopiuj
@Transactional
public EventEditDTO findEventToEdit(AppUser currentUser, Long id) {
    Optional<Event> eventOpt = eventRepository.findById(id);
    if (eventOpt.isPresent()) {
        Event event = eventOpt.get();
        if (!currentUser.equals(event.getOrganizer())) {
            throw new AccessDeniedException("Access is denied");
        }

        return EventEditDTOMapper.mapToEventEditDTO(event);
    }

    throw new EventNotFoundException("Event with ID " + id + " not found");
}
edytowany 1x, ostatnio: Scarilt
RequiredNickname
poczytaj o tym jak używać optionala. Podpowiedź: na pewno nie tak jak nullchecka w ifie...
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:około 10 godzin
  • Lokalizacja:Laska, z Polski
  • Postów:10064
1

Nie ważne jest czy "się powinno" czy "nie powinno". Istotne jest jakie to ma konsekwencje.

Zalety użycia @Transactional w teście:

  • Na ten moment testy się odpalają

Wady:

  • Przywiązanie do frameworka
  • Słaby design testów, bo taką adnotacją trudniej jest kontrolować w testach co się dzieje - trudno jest z czymś takim łatwo wydzielić np helper.
  • Nie można odpalić testów bez springa (i np wstrzyknąć fejkowej implementacji)

Rozważ wady i zalety, i sam zdecyduj co jest dla Ciebie lepszym wyjściem.

Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 23 godziny
0

Możesz dodać do Encji: @OneToOne(fetch = FetchType.EAGER). I chyba wtedy możesz wywalić @Trasactonal z testu, ale teraz z głowy piszę
Natomiast zastanowiłbym się nad tym, czy zamiast referencji nie lepiej trzymać Id tego drugie obiektu


"Nie popełnia błędów tylko ten, kto nic nie robi"
edytowany 1x, ostatnio: Black007
MI
  • Rejestracja:około 9 lat
  • Ostatnio:minuta
  • Postów:110
0
Black007 napisał(a):

Możesz dodać do Encji: @OneToOne(fetch = FetchType.EAGER). I chyba wtedy możesz wywalić @Trasactonal z testu, ale teraz z głowy piszę
Natomiast zastanowiłbym się nad tym, czy zamiast referencji nie lepiej trzymać Id tego drugie obiektu

i wtedy w teście, możesz uderzyć to im id'ku za pomocą repo (ja raczej korzystam w entityManagera) do encji do której chcesz się dostać. Wtedy @Transactional w teście nie będzie potrzebny.

SC
  • Rejestracja:około rok
  • Ostatnio:około 9 godzin
  • Postów:19
0

@Black007 adnotacja @OneToOne ma domyślnie ustawione FetchType.EAGER.

@Black007 @MateInf chodzi o to żeby wgl. usunąć relację OneToOne i trzymać tylko id obiektu?

edytowany 2x, ostatnio: Scarilt
MI
  • Rejestracja:około 9 lat
  • Ostatnio:minuta
  • Postów:110
2
Scarilt napisał(a):

@Black007 @MateInf chodzi o to żeby wgl. usunąć relację OneToOne i trzymać tylko id obiektu?

to miałem na myśli

SC
  • Rejestracja:około rok
  • Ostatnio:około 9 godzin
  • Postów:19
0

@MateInf mam jeszcze jeden problem - odkąd zacząłem dodawać dane testowe przez plik sql to nie przechodzą mi testy, które zapisują coś w bazie. W tym pliku dodaję np. 15 użytkowników z id 1, 2, 3 itd., z góry określam ich id, bo muszę wiedzieć w testach co wyciągnąć i później w teście, który ma utworzyć nowego użytkownika dostaję błąd związany z naruszeniem ograniczenia klucza głównego lub indeksu unikalnego. W każdej encji mam adnotację @GeneratedValue(strategy = GenerationType.IDENTITY). Wcześniej dodawałem dane testowe przez repozytoria Spring Data JPA i było dobrze. W czym tu jest problem?

MA
Nie wystarczy usunąć, trzeba jeszcze dać restart identity i zobacz co robi metoda getReferenceById. Pozderki, można zamykać
SC
@Majksu: ALTER TABLE app_user ALTER COLUMN id RESTART WITH 1; takim poleceniem? Zrobiłem coś takiego dla wszystkich tabel i nie pomogło.
MI
  • Rejestracja:około 9 lat
  • Ostatnio:minuta
  • Postów:110
0
Scarilt napisał(a):

@MateInf mam jeszcze jeden problem - odkąd zacząłem dodawać dane testowe przez plik sql to nie przechodzą mi testy, które zapisują coś w bazie. W tym pliku dodaję np. 15 użytkowników z id 1, 2, 3 itd., z góry określam ich id, bo muszę wiedzieć w testach co wyciągnąć i później w teście, który ma utworzyć nowego użytkownika dostaję błąd związany z naruszeniem ograniczenia klucza głównego lub indeksu unikalnego. W każdej encji mam adnotację @GeneratedValue(strategy = GenerationType.IDENTITY). Wcześniej dodawałem dane testowe przez repozytoria Spring Data JPA i było dobrze. W czym tu jest problem?

Nie wiem, stackOverFlow albo chat gpt pewnie wie. Natomiast, po co ustawiasz z góry idiki? Logicznie myśląc, ze wszystkich pól które może jakkolwiek być najbardziej "nadzorowane" przez omry (pewnie hibernate) - IDki są zdecydowanie tymi polami. Po co więc ingerować swoimi działaniami jak się nie zna świetnie bebechów co się tam dzieje. Nie możesz np. ustawić sobie unikatowe nazwy userów i w teście po tych userach wyciągać rekordy z bazy do testu ?

edytowany 1x, ostatnio: MateInf
PI
  • Rejestracja:ponad 9 lat
  • Ostatnio:4 miesiące
  • Postów:2787
0

Tak to jest jak metody serwisowe zwracają encje JPA zamiast zmapowane DTOsy :P

Black007
DTO to raczej warstwa prezentacji (np. Rest Controller), serwisy w ujęciu "springowym" mają za zadanie właśnie zarządzać sesją
PI
Nope. Owszem zarządzać sesją, ale jak zwracają encje JPA zamiast zmapowane DTOsy, to się kończy jak w niniejszym wątku.
Black007
Yes. autorowi wątku wyciekła jedna warstwa do drugiej, jakby nie używać tego repo, to nie byłoby problemu
Black007
@Pinek: po co ci DTO w warstwie serwisów? Sama nazwa Data Tranfer Object sugeruje ze obiekt jest uzywany do transferu danych. Fasada może zwracać dto. A też nie musi, bo można wywołania jej metod "ubrac" w adaptery
PI
Po to, żeby np właśnie nie mieć tysiąca dziwnych błędów z transakcyjnością. Poza tym odkąd takie DTOsy mam w serwisach, żyje mi się o wiele łatwiej. No i znajomi programiści też tak robią.
KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:13 dni
  • Lokalizacja:Silesia/Marki
  • Postów:5505
1

Jak tak oftopujemy to:

Tak to jest jak metody serwisowe zwracają encje JPA zamiast zmapowane DTOsy 😛

DTO to raczej warstwa prezentacji (np. Rest Controller), serwisy w ujęciu "springowym" mają za zadanie właśnie zarządzać sesją — Black007 dziś, 07:09

Nope. Owszem zarządzać sesją, ale jak zwracają encje JPA zamiast zmapowane DTOsy, to się kończy jak w niniejszym wątku. — Pinek 1 minuta temu

Pracuję już 12 lat i widziałem chyba wszystkie możliwe rozwiązania

  1. encja na twarz i pchasz
  2. encje w serwisach,w kontrolerach dto
  3. encje nie wychodzą z repo
  4. w ogóle nie używamy hibernate/JPA

Przyznam szczerze iż im wyższy liczba tym bardziej podoba mi sie rozwiązanie


Mama called me disappointment, Papa called me fat
Każdego eksperta można zastąpić backendowcem który ma się douczyć po godzinach. Tak zostałem ekspertem AI, Neo4j i Nest.js . Przez mianowanie
PI
encje nie wychodzą z repo z repo czy z serwisów?
PI
Czyli repo w sobie robi już mapowanie jakieś?
KamilAdam
Tak, repo robi dwie rzeczy. Użuwa JPA i mapuje
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 10 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
2
Pinek napisał(a):

Tak to jest jak metody serwisowe zwracają encje JPA zamiast zmapowane DTOsy :P

DTOsy rozwiązują część problemów i generują zupełnie nowe, jeszcze bardziej porypane:
mappery, detached entities.
Szczególnie zapis jakiegoś złożonego obiektu do bazy bywa zabawny. W jednym korpo architekci wymyślili, że będą różne DTOsy (ETO, DTO,CTO :-)) - każdy na kolejnym poziomie. Z samych maperów można było zrobić doktorat.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 1x, ostatnio: jarekr000000
PI
  • Rejestracja:ponad 9 lat
  • Ostatnio:4 miesiące
  • Postów:2787
0
jarekr000000 napisał(a):
Pinek napisał(a):

Tak to jest jak metody serwisowe zwracają encje JPA zamiast zmapowane DTOsy :P

DTOsy rozwiązują część problemów i generują zupełnie nowe, jeszcze bardziej porypane:
mappery, detached entities.
Szczególnie zapis jakiegoś złożonego obiektu do bazy bywa zabawny. W jednym korpo architekci wymyślili, że będą różne DTOsy (ETO, DTO,CTO :-)) - każdy na kolejnym poziomie. Z samych maperów można było zrobić doktorat.

No nie wiem - w obecnym projekcie serwisy zwracają DTOsy i czuję się w tym o wiele lepiej niż w wielu poprzednich projektach, gdzie zwracane były encje JPA.

SC
  • Rejestracja:około rok
  • Ostatnio:około 9 godzin
  • Postów:19
0

@MateInf użytkownicy mają unikatowe nazwy, tzn. email jest unikatowy, ale mam endpointy i metody w serwisach, które wyciągają użytkownika po id i żeby napisać test dla takiej metody muszę wiedzieć jakie id ma konkretny użytkownik.

lambdadziara
testuj findById w jednym test case, a saveUser w innym test case i uzywaj @BeforeEach do usuwania danych/inserta z plikow pomiedzy kazdym test casem
SC
@lambdadziara: testuję tak, tylko czasami do testu potrzebuję obiekt użytkownika i stąd to użycie metody getReferenceById.
Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 23 godziny
0

Jak już wróciliśmy z krainy opfftopa...
Mam 18 lat doświadczenia 😛
I widzę że:
@Scarilt warstwy Ci wyciekają. Ten test nie może nie być Transactional, bo jawnie używasz repository!
Pewnie w teście potrzebujesz wartości, która jest zwracana przez to repo.
Zamiast repo z wywołaną metodą wrzuć tą wartość (która jest zwracana przez repo) i wywal Transactional.


"Nie popełnia błędów tylko ten, kto nic nie robi"
MA
Chcesz powiedzieć, że nie może użyć repo żeby sobie wyciągnąć cokolwiek by chciał w teście, skoro ma to już w bazie? Pomijając design, to w czym problem?
Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 23 godziny
0
Kopiuj
@Test
void shouldReturnEventToEdit() {
    // when
    var givenAppUser = new AppUser();
    var eventToEdit = organizerEventService.findEventToEdit(givenAppUser, 5L);
    // then
    assertThat(eventToEdit.getName()).isEqualTo("Java Dev Talks #5");
    assertThat(eventToEdit.getEventImageData()).isNotBlank();
    assertThat(eventToEdit.getEventImage()).isNull();
    assertThat(eventToEdit.getEventType()).isEqualTo(MEETING);
    assertThat(eventToEdit.getDateTime()).isEqualTo(TOMORROW.plusMonths(1).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    assertThat(eventToEdit.getLanguage()).isEqualTo("polski");
    assertThat(eventToEdit.getAdmission()).isEqualTo(FREE);
    assertThat(eventToEdit.getCity()).isEqualTo("Rzeszów");
    assertThat(eventToEdit.getLocation()).isEqualTo("WSIiZ");
    assertThat(eventToEdit.getAddress()).isEqualTo("Sucharskiego 2, 35-225 Rzeszów");
    assertThat(eventToEdit.getDescription()).isEqualTo("Spotkanie rzeszowskiej grupy pasjonatów języka Java.");
}

Coś w ten deseń


"Nie popełnia błędów tylko ten, kto nic nie robi"
edytowany 1x, ostatnio: Black007
SC
  • Rejestracja:około rok
  • Ostatnio:około 9 godzin
  • Postów:19
1

@Black007 jeśli chodzi o ten problem z pierwszego posta to mam jakieś tipy jak to naprawić, ale na razie muszę poprawnie dodać dane testowe. Pisałem wyżej o tym.

Black007
wybacz, tak mocno zafixowałem się na tym offtopie że pominąłem :D
Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 23 godziny
0

@Majksu
Chodzi o to, że zwykle w Springu masz tak: JpaRepo jest używane (czyli wstrzyknięte) przez service. Service ma jakąś metodę, która używa tego repo, np. dostałem na wejściu imię i nazwisko i chce to zapisać, Robię sobie new Person i ustawiam tam te dane i zapisuje. Ta metoda jest oznaczona @transactional, czyli aop springa tworzy transakcje, a po wyjściu ją commituje (skrót myślowy tutaj). I teraz mam "Klineta" tego serwisu, np. Fasadę lub controller restowy. I ta fasada, czy controller nic nie wiedzą o czymś takim jak sesja bazodanowa, to też same nie muszą mieć transactional.
Teraz pytanie, czy majac taki podział, to czy chcę testować zapis do bazy? Powinien testować funkcjonalność, a test czy zapisuje się do bazy, tak naprawdę testuje czy baza danych działa.


"Nie popełnia błędów tylko ten, kto nic nie robi"
SC
  • Rejestracja:około rok
  • Ostatnio:około 9 godzin
  • Postów:19
0

@Black007 jaką funkcjonalność w ten sposób przetestujesz? Ja myślę, że dla kontrolerów powinniśmy pisać testy integracyjne (tutaj różne osoby używają różnych nazw na ten typ testów), w których np. za pomocą RestTemplate strzelisz na endpoint i przelecisz od kontrolera do samej bazy. A Ty chciałbyś mockować odpowiedzi z metod repozytoriów, czy nawet z metod serwisów? W kontrolerach chyba nie masz skomplikowanej logiki, tylko raczej wywołanie jakiejś metody z serwisu i ewentualnie opakowanie zwróconego obiektu w obiekt typu ResponseEntity.

Black007
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 23 godziny
1

W kontrolerach chyba nie masz skomplikowanej logiki, tylko raczej wywołanie jakiejś metody z serwisu i ewentualnie opakowanie zwróconego obiektu w obiekt typu ResponseEntity.

Dokładnie tak. Controlery tylko wywołują fasade.
Dlatego testy integracyjne mogą być na poziomie fasady. Czyli tego co ma się tam dziać, logiki biznesowej.
Można użyć testów czarnej skrzynki.
Wrzucasz parametry i sprawdzasz oczekiwaną wartość.
Albo białej skrzynki. Mockujesz np. Repository i sprawdzasz czy dany mock był wywołany np. 1 raz z danymi parametrami.


"Nie popełnia błędów tylko ten, kto nic nie robi"
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)