Skip to content

Commit a2ce5fd

Browse files
Cathy0123456789Abi107717
authored andcommitted
Development: Add German job description for export (#1998)
1 parent db0a8a1 commit a2ce5fd

5 files changed

Lines changed: 118 additions & 5 deletions

File tree

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ dependencies {
256256
testImplementation "org.testcontainers:junit-jupiter:${testcontainersVersion}"
257257
testImplementation "org.testcontainers:mysql:${testcontainersVersion}"
258258
testImplementation "org.testcontainers:testcontainers:${testcontainersVersion}"
259+
// PDF text extraction for tests
260+
testImplementation "org.apache.pdfbox:pdfbox:3.0.6"
259261
// Annotation processors
260262
annotationProcessor "org.glassfish.jaxb:jaxb-runtime:4.0.6"
261263
annotationProcessor "org.hibernate.orm:hibernate-jpamodelgen"

src/main/java/de/tum/cit/aet/core/service/PDFExportService.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ public Resource exportApplicationToPDF(ApplicationDetailDTO app, Map<String, Str
6262

6363
if (app.jobId() != null) {
6464
JobDetailDTO job = jobService.getJobDetails(app.jobId());
65+
// Determine job description language and content
66+
String lang = labels.getOrDefault("lang", "en");
67+
String descriptionForExport = selectJobDescriptionForLang(job.jobDescriptionEN(), job.jobDescriptionDE(), lang);
68+
6569
// Overview Section if no in preview
6670
builder
6771
.setOverviewTitle(labels.get("overview"))
@@ -76,7 +80,7 @@ public Resource exportApplicationToPDF(ApplicationDetailDTO app, Map<String, Str
7680
.addOverviewItem(labels.get("startDate"), formatDate(job.startDate()))
7781
.addOverviewItem(labels.get("endDate"), formatDate(job.endDate()))
7882
.setOverviewDescriptionTitle(labels.get("jobDescription"))
79-
.setOverviewDescription(job.jobDescriptionEN());
83+
.setOverviewDescription(descriptionForExport);
8084
}
8185

8286
// Personal Statements Group
@@ -175,8 +179,12 @@ public Resource exportJobToPDF(UUID jobId, Map<String, String> labels) {
175179
)
176180
);
177181

182+
// Determine job description language and content
183+
String lang = labels.getOrDefault("lang", "en");
184+
String descriptionForExport = selectJobDescriptionForLang(job.jobDescriptionEN(), job.jobDescriptionDE(), lang);
185+
178186
// Job Details Section
179-
addJobDetailsSection(builder, labels, job.jobDescriptionEN());
187+
addJobDetailsSection(builder, labels, descriptionForExport);
180188

181189
// Research Group Section
182190
addResearchGroupSection(builder, job.researchGroup(), labels);
@@ -237,8 +245,12 @@ public Resource exportJobPreviewToPDF(JobFormDTO jobFormDTO, Map<String, String>
237245
)
238246
);
239247

248+
// Determine job description based on requested language
249+
String lang = labels.getOrDefault("lang", "en");
250+
String descriptionForExport = selectJobDescriptionForLang(jobFormDTO.jobDescriptionEN(), jobFormDTO.jobDescriptionDE(), lang);
251+
240252
// Job Details Section
241-
addJobDetailsSection(builder, labels, jobFormDTO.jobDescriptionEN());
253+
addJobDetailsSection(builder, labels, descriptionForExport);
242254

243255
// Metadata
244256
builder.setMetadata(buildMetadataText(labels));
@@ -424,4 +436,25 @@ private String formatAddress(String street, String postalCode, String city) {
424436

425437
return String.join(", ", parts);
426438
}
439+
440+
/**
441+
* Selects the appropriate job description based on the requested language and availability
442+
*
443+
* @param englishDesc English job description
444+
* @param germanDesc German job description
445+
* @param lang requested language ("en" or "de")
446+
* @return the selected job description for export or a "-" if none is available
447+
*/
448+
private String selectJobDescriptionForLang(String englishDesc, String germanDesc, String lang) {
449+
String primary = "de".equalsIgnoreCase(lang) ? germanDesc : englishDesc;
450+
String secondary = "de".equalsIgnoreCase(lang) ? englishDesc : germanDesc;
451+
452+
if (primary != null && !primary.trim().isEmpty()) {
453+
return primary;
454+
} else if (secondary != null && !secondary.trim().isEmpty()) {
455+
return secondary;
456+
} else {
457+
return "-";
458+
}
459+
}
427460
}

src/main/webapp/app/shared/language/pdf-labels.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ function getOverviewItemLabel(translate: TranslateService): Record<string, strin
6363
fundingType: `${translate.instant('jobDetailPage.labels.fundingType')}:`,
6464
startDate: `${translate.instant('jobDetailPage.labels.startDate')}:`,
6565
endDate: `${translate.instant('jobDetailPage.labels.applicationEndDate')}:`,
66+
lang: translate.getCurrentLang(),
6667
};
6768
}
6869

src/test/java/de/tum/cit/aet/core/web/rest/PDFExportResourceTest.java

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,17 @@
3232
import java.time.LocalDate;
3333
import java.util.Map;
3434
import java.util.UUID;
35+
import java.util.stream.Stream;
36+
import org.apache.pdfbox.Loader;
37+
import org.apache.pdfbox.io.RandomAccessReadBuffer;
38+
import org.apache.pdfbox.pdmodel.PDDocument;
39+
import org.apache.pdfbox.text.PDFTextStripper;
3540
import org.junit.jupiter.api.BeforeEach;
3641
import org.junit.jupiter.api.Nested;
3742
import org.junit.jupiter.api.Test;
43+
import org.junit.jupiter.params.ParameterizedTest;
44+
import org.junit.jupiter.params.provider.Arguments;
45+
import org.junit.jupiter.params.provider.MethodSource;
3846
import org.springframework.beans.factory.annotation.Autowired;
3947
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
4048
import org.springframework.boot.test.context.SpringBootTest;
@@ -76,7 +84,6 @@ class PDFExportResourceTest extends AbstractResourceTest {
7684
Applicant applicant;
7785
Applicant applicantWithWebsiteAndLinkedin;
7886
Job job;
79-
Job jobWithNulls;
8087
Application application;
8188
Application applicationWithWebsiteAndLinkedin;
8289

@@ -214,6 +221,16 @@ private ApplicationPDFRequest createApplicationPdfRequest(Application applicatio
214221
return new ApplicationPDFRequest(appDto, labels);
215222
}
216223

224+
// Helper to extract text from a PDF byte array using PDFBox
225+
private String extractTextFromPdf(byte[] pdfBytes) {
226+
try (PDDocument doc = Loader.loadPDF(new RandomAccessReadBuffer(pdfBytes))) {
227+
PDFTextStripper stripper = new PDFTextStripper();
228+
return stripper.getText(doc);
229+
} catch (Exception e) {
230+
throw new RuntimeException("Failed to extract text from PDF", e);
231+
}
232+
}
233+
217234
@Nested
218235
class ExportApplicationToPDF {
219236

@@ -268,7 +285,7 @@ void shouldExportApplicationWithMasterDegreeNameNull() {
268285
"Test",
269286
"Test"
270287
);
271-
ApplicationPDFRequest request = createApplicationPdfRequest(application, job);
288+
ApplicationPDFRequest request = createApplicationPdfRequest(appWithMaster, job);
272289

273290
byte[] result = asApplicant(applicantWithMasterNameNull).postAndReturnBytes(
274291
BASE_URL + "/application/pdf",
@@ -329,6 +346,60 @@ void shouldHandleJobWithNullFields() {
329346

330347
assertValidPdf(result);
331348
}
349+
350+
@ParameterizedTest
351+
@MethodSource("jobLanguageProvider")
352+
void exportJobToPDFLanguageSelection(String enDescription, String deDescription, String requestedLang, String expectedSubstring) {
353+
Job jobToTest = JobTestData.savedAll(
354+
jobRepository,
355+
"Lang Test Job",
356+
"AI",
357+
"CS",
358+
professor,
359+
group,
360+
Campus.GARCHING,
361+
LocalDate.now(),
362+
LocalDate.now(),
363+
20,
364+
3,
365+
FundingType.FULLY_FUNDED,
366+
enDescription,
367+
deDescription,
368+
JobState.PUBLISHED
369+
);
370+
371+
Map<String, String> labels = createCompleteLabelsMap();
372+
if (requestedLang != null) {
373+
labels.put("lang", requestedLang);
374+
}
375+
376+
byte[] pdf = api
377+
.withoutPostProcessors()
378+
.postAndReturnBytes(BASE_URL + "/job/" + jobToTest.getJobId() + "/pdf", labels, 200, MediaType.APPLICATION_PDF);
379+
380+
assertValidPdf(pdf);
381+
String text = extractTextFromPdf(pdf);
382+
assertThat(text).contains(expectedSubstring);
383+
}
384+
385+
static Stream<Arguments> jobLanguageProvider() {
386+
return Stream.of(
387+
// both present -> select en
388+
Arguments.of("EN unique-en-xyz", "DE unique-de-xyz", "en", "unique-en-xyz"),
389+
// both present -> select de
390+
Arguments.of("EN unique-en-xyz", "DE unique-de-xyz", "de", "unique-de-xyz"),
391+
// only EN present -> requested DE should fallback to EN
392+
Arguments.of("EN only unique-en-only", null, "de", "unique-en-only"),
393+
// only DE present -> requested EN should fallback to DE
394+
Arguments.of(null, "DE only unique-de-only", "en", "unique-de-only"),
395+
// de whitespace with en present -> fallback to en
396+
Arguments.of("EN fallback unique-en-ws", " ", "de", "unique-en-ws"),
397+
// both empty -> '-'
398+
Arguments.of("", "", null, "-"),
399+
// none present -> expect '-'
400+
Arguments.of(null, null, null, "-")
401+
);
402+
}
332403
}
333404

334405
@Nested

src/test/webapp/app/shared/language/pdf-labels.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ function expectOverviewLabels(labels: Record<string, string>) {
2020
expect(labels).toHaveProperty('fundingType');
2121
expect(labels).toHaveProperty('startDate');
2222
expect(labels).toHaveProperty('endDate');
23+
expect(labels).toHaveProperty('lang');
2324
}
2425

2526
function expectFooterLabels(labels: Record<string, string>) {
@@ -75,6 +76,8 @@ describe('PDF Labels', () => {
7576
expectOverviewLabels(labels);
7677
expectFooterLabels(labels);
7778

79+
expect(labels.lang).toBe(translate.getCurrentLang());
80+
7881
expect(labels).toHaveProperty('personalStatements');
7982
expect(labels).toHaveProperty('personalInformation');
8083
expect(labels).toHaveProperty('applicantInfo');
@@ -158,6 +161,8 @@ describe('PDF Labels', () => {
158161
expectOverviewLabels(labels);
159162
expectFooterLabels(labels);
160163

164+
expect(labels.lang).toBe(translate.getCurrentLang());
165+
161166
expect(labels).toHaveProperty('jobDetails');
162167
expect(labels).toHaveProperty('description');
163168
expect(labels).toHaveProperty('tasksResponsibilities');
@@ -245,6 +250,7 @@ describe('PDF Labels', () => {
245250
'metaEndText',
246251
'page',
247252
'of',
253+
'lang',
248254
]);
249255
});
250256
});

0 commit comments

Comments
 (0)