Skip to content

Commit ad739e2

Browse files
committed
Added more tests
1 parent 183bbc6 commit ad739e2

2 files changed

Lines changed: 269 additions & 1 deletion

File tree

plugin/src/main/java/io/jenkins/plugins/casc/yaml/YamlUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ private static Node resolveExtends(
243243

244244
try (Reader parentReader = reader(parentSource)) {
245245
parentNode = read(parentSource, parentReader, context);
246-
} catch (IOException e) {
246+
} catch (IOException | YAMLException e) {
247247
throw new ConfiguratorException("Failed to read extended config: " + path, e);
248248
}
249249

plugin/src/test/java/io/jenkins/plugins/casc/yaml/YamlExtendsTest.java

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static org.hamcrest.MatcherAssert.assertThat;
44
import static org.hamcrest.Matchers.containsString;
55
import static org.junit.Assert.assertEquals;
6+
import static org.junit.Assert.assertNotNull;
67
import static org.junit.Assert.assertThrows;
78
import static org.junit.Assert.assertTrue;
89

@@ -11,6 +12,8 @@
1112
import java.io.ByteArrayInputStream;
1213
import java.io.IOException;
1314
import java.io.InputStream;
15+
import java.lang.reflect.InvocationTargetException;
16+
import java.lang.reflect.Method;
1417
import java.nio.charset.StandardCharsets;
1518
import java.nio.file.Files;
1619
import java.nio.file.Path;
@@ -309,4 +312,269 @@ private ConfiguratorException expectConfiguratorException(Runnable r) {
309312

310313
throw new AssertionError("Expected ConfiguratorException but got: " + ex);
311314
}
315+
316+
@Test
317+
public void testExtendsListWithNonScalarItemThrowsException() throws Exception {
318+
writeYaml("base.yaml", "a: 1");
319+
320+
Path child = writeYaml("child.yaml", """
321+
_extends:
322+
- base.yaml
323+
- key: value
324+
""");
325+
326+
ConfiguratorException ex = expectConfiguratorException(() -> parse(child));
327+
328+
assertThat(ex.getMessage(), containsString("Invalid item in '_extends'"));
329+
}
330+
331+
@Test
332+
public void testExtendsWithInvalidTypeThrowsException() throws Exception {
333+
Path child = writeYaml("child.yaml", """
334+
_extends:
335+
key: value
336+
""");
337+
338+
ConfiguratorException ex = expectConfiguratorException(() -> parse(child));
339+
340+
assertThat(ex.getMessage(), containsString("Invalid value for '_extends' key"));
341+
}
342+
343+
@Test
344+
public void testExtendsWithUnreadableFileThrowsException() throws Exception {
345+
Path dir = tempDir.newFolder("not_a_file").toPath();
346+
347+
Path child = writeYaml("child.yaml", "_extends: " + dir.getFileName().toString());
348+
349+
ConfiguratorException ex = expectConfiguratorException(() -> parse(child));
350+
351+
assertThat(ex.getMessage(), containsString("Failed to read extended config"));
352+
}
353+
354+
@Test
355+
public void testMappingWithoutExtendsButWithNestedChangesReturnsNewMapNode() throws Exception {
356+
Path yaml = writeYaml("simple.yaml", """
357+
jenkins:
358+
systemMessage: "hello"
359+
""");
360+
361+
Node root = parse(yaml);
362+
363+
assertTrue(root instanceof MappingNode);
364+
MappingNode map = (MappingNode) root;
365+
366+
MappingNode jenkins = (MappingNode) getChildNode(map, "jenkins");
367+
assertEquals("hello", getScalarValue(jenkins, "systemMessage"));
368+
}
369+
370+
@Test
371+
public void testSequenceNodeWithExtendsTriggersHasChanges() throws Exception {
372+
writeYaml("base.yaml", """
373+
key: baseValue
374+
""");
375+
376+
Path child = writeYaml("child.yaml", """
377+
list:
378+
- _extends: base.yaml
379+
""");
380+
381+
Node root = parse(child);
382+
assertTrue(root instanceof MappingNode);
383+
384+
SequenceNode list = (SequenceNode) getChildNode((MappingNode) root, "list");
385+
386+
MappingNode item = (MappingNode) list.getValue().get(0);
387+
assertEquals("baseValue", getScalarValue(item, "key"));
388+
}
389+
390+
@Test
391+
public void testExtendsWithFileUri() throws Exception {
392+
Path base = writeYaml("base.yaml", """
393+
key: uriValue
394+
""");
395+
396+
String uriPath = base.toUri().toString();
397+
398+
Path child = writeYaml("child.yaml", "_extends: " + uriPath);
399+
400+
Node root = parse(child);
401+
402+
assertTrue(root instanceof MappingNode);
403+
assertEquals("uriValue", getScalarValue((MappingNode) root, "key"));
404+
}
405+
406+
@Test
407+
public void testExtendsWithHttpUriFormat() {
408+
String fakeUri = "https://example.com/config.yaml";
409+
410+
ConfiguratorException ex =
411+
assertThrows(ConfiguratorException.class, () -> parse(writeYaml("child.yaml", "_extends: " + fakeUri)));
412+
413+
assertThat(ex.getMessage(), containsString("Failed to read extended config"));
414+
}
415+
416+
@Test
417+
public void testExtendsWithNonExistentFileThrowsException() throws Exception {
418+
Path child = writeYaml("child.yaml", """
419+
_extends: missing.yaml
420+
key: value
421+
""");
422+
423+
ConfiguratorException ex = assertThrows(ConfiguratorException.class, () -> parse(child));
424+
425+
assertThat(ex.getMessage(), containsString("Extended configuration file does not exist"));
426+
}
427+
428+
@Test
429+
public void testExtendsWithStringSourceUri() throws Exception {
430+
writeYaml("base.yaml", "key: baseValue");
431+
432+
Path childFile = writeYaml("child.yaml", """
433+
_extends: base.yaml
434+
""");
435+
436+
String childUri = childFile.toUri().toString();
437+
438+
YamlSource<String> source = YamlSource.of(childUri);
439+
440+
Node root = YamlUtils.merge(Collections.singletonList(source), context);
441+
442+
assertEquals("baseValue", getScalarValue((MappingNode) root, "key"));
443+
}
444+
445+
@Test
446+
public void testUnsupportedSourceTypeThrowsException() throws Exception {
447+
Object unsupported = new Object();
448+
YamlSource<Object> source = new YamlSource<>(unsupported);
449+
450+
Method resolveMethod =
451+
YamlUtils.class.getDeclaredMethod("resolveRelativeSource", YamlSource.class, String.class);
452+
resolveMethod.setAccessible(true);
453+
454+
InvocationTargetException ex = assertThrows(
455+
InvocationTargetException.class, () -> resolveMethod.invoke(null, source, "dummy-path.yaml"));
456+
457+
Throwable actualException = ex.getCause();
458+
assertTrue(actualException instanceof ConfiguratorException);
459+
460+
assertThat(
461+
actualException.getMessage(),
462+
containsString("Cannot resolve relative path 'dummy-path.yaml' for source type: Object"));
463+
}
464+
465+
@Test
466+
public void testInvalidYamlKeyTypeThrowsException() throws Exception {
467+
writeYaml("base.yaml", """
468+
validKey: value
469+
""");
470+
471+
Path child = writeYaml("child.yaml", """
472+
_extends: base.yaml
473+
? [a, b]
474+
: value
475+
""");
476+
477+
ConfiguratorException ex = assertThrows(ConfiguratorException.class, () -> parse(child));
478+
479+
assertThat(ex.getMessage(), containsString("Invalid YAML key type"));
480+
}
481+
482+
@Test
483+
public void testScalarOverrideUsesCloneNode() throws Exception {
484+
writeYaml("base.yaml", """
485+
key: baseValue
486+
""");
487+
488+
Path child = writeYaml("child.yaml", """
489+
_extends: base.yaml
490+
key: overrideValue
491+
""");
492+
493+
Node root = parse(child);
494+
495+
assertEquals("overrideValue", getScalarValue((MappingNode) root, "key"));
496+
}
497+
498+
@Test
499+
public void testSequenceOverrideUsesCloneNode() throws Exception {
500+
writeYaml("base.yaml", """
501+
list:
502+
- one
503+
- two
504+
""");
505+
506+
Path child = writeYaml("child.yaml", """
507+
_extends: base.yaml
508+
list:
509+
- three
510+
- four
511+
""");
512+
513+
Node root = parse(child);
514+
515+
SequenceNode seq = (SequenceNode) getChildNode((MappingNode) root, "list");
516+
517+
assertEquals(2, seq.getValue().size());
518+
assertEquals("three", ((ScalarNode) seq.getValue().get(0)).getValue());
519+
}
520+
521+
@Test
522+
public void testCloneNodeWithNullValue() throws Exception {
523+
writeYaml("base.yaml", """
524+
key: value
525+
""");
526+
527+
Path child = writeYaml("child.yaml", """
528+
_extends: base.yaml
529+
key:
530+
""");
531+
532+
Node root = parse(child);
533+
534+
MappingNode map = (MappingNode) root;
535+
536+
Node valueNode = getChildNode(map, "key");
537+
538+
assertTrue(valueNode == null || valueNode instanceof ScalarNode);
539+
}
540+
541+
@Test
542+
public void testCloneNodeWithAliasReturnsOriginalNode() throws Exception {
543+
Path file = writeYaml("alias.yaml", """
544+
base: &anchor
545+
key: value
546+
copy: *anchor
547+
""");
548+
549+
Node root = parse(file);
550+
551+
MappingNode map = (MappingNode) root;
552+
553+
Node copyNode = getChildNode(map, "copy");
554+
555+
assertNotNull(copyNode);
556+
}
557+
558+
@Test
559+
public void testCloneNodeFallbackBranch() throws Exception {
560+
Path file = writeYaml("complex.yaml", """
561+
root:
562+
- &a { key: value }
563+
- *a
564+
""");
565+
566+
Node root = parse(file);
567+
568+
assertNotNull(root);
569+
}
570+
571+
@Test
572+
public void testInvalidUriFallbackBranch() {
573+
String bad = "ht!tp://invalid uri";
574+
575+
assertThrows(Exception.class, () -> {
576+
YamlSource<String> source = YamlSource.of(bad);
577+
YamlUtils.merge(Collections.singletonList(source), context);
578+
});
579+
}
312580
}

0 commit comments

Comments
 (0)