3 Commits

Author SHA1 Message Date
ksan b53805ee3f testing new ci/cd
CI/CD / Backend Unit Tests (push) Successful in 1m48s
CI/CD / Deploy (push) Has been skipped
CI/CD / Backend Unit Tests (pull_request) Successful in 1m45s
CI/CD / Deploy (pull_request) Has been skipped
2026-06-10 18:03:56 +02:00
ksan 28bd6a8cbc fixed jwtservicetest not to enclude entire spring boot test
CI / backend-tests (push) Successful in 2m10s
2026-06-10 14:56:19 +02:00
ksan d598fdca1a removed cache
CI / backend-tests (push) Failing after 2m28s
2026-06-10 14:44:15 +02:00
7 changed files with 142 additions and 30 deletions
+90 -14
View File
@@ -1,30 +1,106 @@
name: CI name: CI/CD
on: on:
push: push:
branches: [dev] branches:
- dev
- main
pull_request: pull_request:
branches: [main] branches:
- main
env:
JAVA_VERSION: "21"
jobs: jobs:
backend-tests: backend-unit-tests:
name: Backend Unit Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
working-directory: backend working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4 steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v4
with: with:
java-version: '21' distribution: temurin
distribution: 'temurin' java-version: ${{ env.JAVA_VERSION }}
cache: gradle
- name: Make Gradle wrapper executable - name: Make Gradle wrapper executable
run: chmod +x ./gradlew run: chmod +x gradlew
- name: Run tests - name: Run unit tests
run: ./gradlew test --no-daemon run: ./gradlew test
# TODO: CD logic — deploy after successful tests on merge to main deploy:
name: Deploy
needs: backend-unit-tests
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
- name: Build boot jar
working-directory: backend
run: |
chmod +x gradlew
./gradlew bootJar
- name: Stage jar for Docker
working-directory: backend
run: |
BOOT_JAR=$(find build/libs -name "*.jar" ! -name "*-plain.jar" | head -n 1)
cp "$BOOT_JAR" app.jar
- name: Login to Docker registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login git.${{ secrets.DOMAIN }} \
-u "${{ secrets.REGISTRY_USER }}" \
--password-stdin
- name: Build Docker image
run: |
docker build \
-t git.${{ secrets.DOMAIN }}/${{ secrets.REGISTRY_USER }}/etf-oglasi-server:latest \
backend/
- name: Push Docker image
run: |
docker push \
git.${{ secrets.DOMAIN }}/${{ secrets.REGISTRY_USER }}/etf-oglasi-server:latest
- name: Deploy to VPS
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/key
chmod 600 ~/.ssh/key
ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts
# Write secrets to VPS (only if they've changed or first deploy)
ssh -i ~/.ssh/key "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \
"mkdir -p ~/programs/etf-oglasi-server/config"
echo "${{ secrets.FIREBASE_CREDENTIALS }}" | \
ssh -i ~/.ssh/key "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \
"cat > ~/programs/etf-oglasi-server/config/firebase.json"
ssh -i ~/.ssh/key "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \
"cd ~/programs/etf-oglasi-server && docker compose pull && docker compose up -d"
+1
View File
@@ -35,6 +35,7 @@ backend/**/*temp.java
backend/src/main/resources/google-services.json backend/src/main/resources/google-services.json
backend/src/main/resources/etf-oglasi-firebase.json backend/src/main/resources/etf-oglasi-firebase.json
backend/src/main/resources/firebase.json
# Java / Eclipse / STS # Java / Eclipse / STS
.classpath .classpath
+5
View File
@@ -0,0 +1,5 @@
FROM eclipse-temurin:21-jre-jammy
WORKDIR /app
COPY app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
@@ -17,7 +17,7 @@ public class FirebaseConfig {
@Bean @Bean
public FirebaseApp firebaseApp() throws IOException { public FirebaseApp firebaseApp() throws IOException {
GoogleCredentials credentials = GoogleCredentials GoogleCredentials credentials = GoogleCredentials
.fromStream(new ClassPathResource("etf-oglasi-firebase.json").getInputStream()) .fromStream(new ClassPathResource("firebase.json").getInputStream())
.createScoped(List.of("https://www.googleapis.com/auth/firebase.messaging")); .createScoped(List.of("https://www.googleapis.com/auth/firebase.messaging"));
FirebaseOptions options = FirebaseOptions.builder() FirebaseOptions options = FirebaseOptions.builder()
@@ -1,47 +1,49 @@
package dev.ksan.etfoglasiserver.service; package dev.ksan.etfoglasiserver.service;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import java.lang.reflect.Field;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
@SpringBootTest @ExtendWith(MockitoExtension.class)
@TestPropertySource(locations = "classpath:application.properties")
class JWTServiceTest { class JWTServiceTest {
@Autowired private JWTService jwtService;
JWTService jwtService;
private org.springframework.security.core.userdetails.UserDetails userDetails(String email) { private static final String TEST_SECRET =
return org.springframework.security.core.userdetails.User "dGVzdHNlY3JldGtleXRoYXRpc2xvbmdlbm91Z2hmb3JibWFjc2hhMjU2dGVzdGtleQ==";
.withUsername(email)
.password("irrelevant") @BeforeEach
.roles("USER") void setUp() throws Exception {
.build(); jwtService = new JWTService();
Field secret = JWTService.class.getDeclaredField("secretKey");
secret.setAccessible(true);
secret.set(jwtService, TEST_SECRET);
} }
@Test @Test
void generateToken_extractEmail_returnsCorrectEmail() { void generateToken_extractEmail_returnsCorrectEmail() {
String token = jwtService.generateToken("alice@example.com"); String token = jwtService.generateToken("alice@example.com");
assertThat(jwtService.extractEmail(token)).isEqualTo("alice@example.com"); assertThat(jwtService.extractEmail(token)).isEqualTo("alice@example.com");
} }
@Test @Test
void validateToken_correctUser_returnsTrue() { void validateToken_correctUser_returnsTrue() {
String token = jwtService.generateToken("alice@example.com"); String token = jwtService.generateToken("alice@example.com");
assertThat(jwtService.validateToken(token, userDetails("alice@example.com"))).isTrue(); assertThat(jwtService.validateToken(token, userDetails("alice@example.com"))).isTrue();
} }
@Test @Test
void validateToken_differentUser_returnsFalse() { void validateToken_differentUser_returnsFalse() {
String token = jwtService.generateToken("alice@example.com"); String token = jwtService.generateToken("alice@example.com");
assertThat(jwtService.validateToken(token, userDetails("bob@example.com"))).isFalse(); assertThat(jwtService.validateToken(token, userDetails("bob@example.com"))).isFalse();
} }
@@ -49,8 +51,12 @@ class JWTServiceTest {
void validateToken_tamperedToken_throwsException() { void validateToken_tamperedToken_throwsException() {
String token = jwtService.generateToken("alice@example.com"); String token = jwtService.generateToken("alice@example.com");
String tampered = token.substring(0, token.length() - 4) + "XXXX"; String tampered = token.substring(0, token.length() - 4) + "XXXX";
assertThatThrownBy(() -> jwtService.validateToken(tampered, userDetails("alice@example.com"))) assertThatThrownBy(() -> jwtService.validateToken(tampered, userDetails("alice@example.com")))
.isInstanceOf(Exception.class); .isInstanceOf(Exception.class);
} }
private org.springframework.security.core.userdetails.UserDetails userDetails(String email) {
return org.springframework.security.core.userdetails.User
.withUsername(email).password("x").roles("USER").build();
}
} }
+24
View File
@@ -0,0 +1,24 @@
name: CI - full
on:
pull_request:
branches: [main]
jobs:
backend-test:
uses: ./.gitea/workflows/backend-test.yaml
frontend-build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Build frontend
run: npx expo export --platform web