Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/content/releases/posts/v0.46.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ links:

- The SSH client library that Galasa uses has been updated to support stronger key algorithms, including `rsa-sha2-256` and `rsa-sha2-512`. See [#2461](https://github.com/galasa-dev/projectmanagement/issues/2461).
- **Galasa managers that use the IP network manager, like the zOS TSO Command SSH and zOS UNIX Command SSH managers, no longer support the RSA/SHA1 signature algorithm when connecting to a server via SSH. If you are using RSA/SHA1, you should upgrade your servers to use a stronger algorithm.**
- The z/OS Program Manager now validates that program names specified in the `@ZosProgram` annotation are **uppercase** and **no longer than 8 characters**. If a name is not uppercase or exceeds eight characters, the z/OS Program Manager rejects it and raises a ZosProgramManagerException with an actionable error message.

## Changes affecting the Galasa Service

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
package dev.galasa.zosprogram.internal;

import java.lang.annotation.Annotation;
Expand All @@ -11,6 +11,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Locale;

import javax.validation.constraints.NotNull;

Expand Down Expand Up @@ -205,6 +206,21 @@ public IZosProgram generateZosProgram(Field field, List<Annotation> annotations)
boolean cics = annotationZosProgram.cics();
String loadlib = nulled(annotationZosProgram.loadlib());
boolean compile = annotationZosProgram.compile();

//Ensure program name does not exceed 8 characters
if (name.length() > 8) {
throw new ZosProgramManagerException("Program name '" + name + "' exceeds 8 characters.");
}


if (!name.equals(name.toUpperCase(Locale.ROOT))) {
throw new ZosProgramManagerException(
"Program name '" + name + "' is not capitalized. Ensure names in the annotation and file names must be uppercase."
);
}




ZosProgramImpl zosProgram = new ZosProgramImpl(this, field, tag, name, location, language, cics, loadlib, compile);
zosPrograms.put(field.getName(), zosProgram);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
package dev.galasa.zosprogram.internal;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collections;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

import javax.validation.constraints.NotNull;

import org.junit.Assert;

import org.junit.Before;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

//import org.powermock.api.mockito.PowerMockito;
//import org.powermock.core.classloader.annotations.PrepareForTest;
//import org.powermock.modules.junit4.PowerMockRunner;
Expand Down Expand Up @@ -47,36 +54,32 @@
import dev.galasa.zosfile.spi.IZosFileSpi;
import dev.galasa.zosprogram.IZosProgram;
import dev.galasa.zosprogram.ZosProgram.Language;
import dev.galasa.zosprogram.ZosProgram;
import dev.galasa.zosprogram.ZosProgramException;
import dev.galasa.zosprogram.ZosProgramManagerException;
import dev.galasa.zosprogram.internal.properties.CICSDatasetPrefix;
import dev.galasa.zosprogram.internal.properties.LanguageEnvironmentDatasetPrefix;
import dev.galasa.zosprogram.internal.properties.ProgramLanguageCompileSyslibs;
import dev.galasa.zosprogram.internal.properties.ProgramLanguageDatasetPrefix;
import dev.galasa.zosprogram.internal.properties.ProgramLanguageLinkSyslibs;
import dev.galasa.zosprogram.internal.ZosProgramManagerImpl;
import dev.galasa.zosprogram.internal.properties.ZosProgramPropertiesSingleton;

//@RunWith(PowerMockRunner.class)
//@PrepareForTest({ProgramLanguageDatasetPrefix.class, LanguageEnvironmentDatasetPrefix.class, ProgramLanguageCompileSyslibs.class, ProgramLanguageLinkSyslibs.class, CICSDatasetPrefix.class})
public class TestZosProgramManagerImpl {
//
// private ZosProgramManagerImpl zosProgramManager;
//
// private ZosProgramManagerImpl zosProgramManagerSpy;
//
// private ZosProgramPropertiesSingleton zosProgramZosProgramPropertiesSingleton;

private ZosProgramManagerImpl zosProgramManager;
private ZosProgramManagerImpl zosProgramManagerSpy;
private ZosProgramPropertiesSingleton zosProgramZosProgramPropertiesSingleton;
//
// private List<IManager> allManagers;
private List<IManager> allManagers;
//
// private List<IManager> activeManagers;
//
// @Mock
// private IFramework frameworkMock;
//
// @Mock
// private IResultArchiveStore resultArchiveStoreMock;
//
// @Mock
private List<IManager> activeManagers;
@Mock
private IFramework frameworkMock;
@Mock
private IResultArchiveStore resultArchiveStoreMock;
// public IManager managerMock;
//
// @Mock
Expand All @@ -87,29 +90,29 @@ public class TestZosProgramManagerImpl {
//
// @Mock
// private IZosFileSpi zosFileSpiMock;
//
// @Mock
// private ArtifactManagerImpl artifactManagerMock;
//
// @Mock
// private IBundleResources bundleResourcesMock;
//
// @Mock
// private IZosBatch zosBatchMock;
@Mock
private ArtifactManagerImpl artifactManagerMock;
@Mock
private IBundleResources bundleResourcesMock;
@Mock
private IZosBatch zosBatchMock;
//
// @Mock
// private IZosBatchJob zosBatchJobMock;
//
// @Mock
// private IZosImage zosImageMock;
//
@Mock
private IZosFileSpi zosFileSpiMock;

@Mock
private ZosManagerImpl zosManagerMock;

@Mock
private IZosImage zosImageMock;
// @Mock
// private ZosProgramImpl zosProgramMock;
//
// @Mock
// private IZosDataset loadlibMock;
//
// private static final String IMAGE = "image";
@Mock
private IZosDataset loadlibMock;
private static final String IMAGE = "image";
//
// private static final String NAME = "NAME";
//
Expand Down Expand Up @@ -358,4 +361,151 @@ public class TestZosProgramManagerImpl {
// return null;
// }
// }

// Setup
static class Dummy {
@ZosProgram(
name = "myprog", // lower-case; should be throw error
location = "source",
imageTag = "A",
language = Language.COBOL,
cics = false,
loadlib = "",
compile = true
)
public IZosProgram myProgField;

@ZosProgram(
name = "MYPROG01", // exactly 8 characters
location = "source",
imageTag = "primary", // will become "PRIMARY"
language = Language.COBOL,
cics = false,
loadlib = "",
compile = true
)
public IZosProgram eightCharField;

@ZosProgram(
name = "MYPROG", // <= 8 characters
location = "source",
imageTag = "A",
language = Language.COBOL,
cics = false,
loadlib = "",
compile = true
)
public IZosProgram validField;
}


@Before
public void setup() throws Exception {
// Using Mockito instead of PowerMockito for simpler, faster unit tests (just a design choice)
MockitoAnnotations.openMocks(this);

zosProgramZosProgramPropertiesSingleton = new ZosProgramPropertiesSingleton();
zosProgramZosProgramPropertiesSingleton.activate();

zosProgramManager = new ZosProgramManagerImpl();
zosProgramManagerSpy = spy(zosProgramManager);

when(artifactManagerMock.getBundleResources(any())).thenReturn(bundleResourcesMock);
when(zosImageMock.getImageID()).thenReturn(IMAGE);
when(frameworkMock.getResultArchiveStore()).thenReturn(resultArchiveStoreMock);
when(resultArchiveStoreMock.getStoredArtifactsRoot()).thenReturn(new File("/").toPath());
when(zosManagerMock.getImageForTag(any())).thenReturn(zosImageMock);

doReturn(frameworkMock).when(zosProgramManagerSpy).getFramework();
doReturn(zosManagerMock).when(zosProgramManagerSpy).getZosManager();
doReturn(zosFileSpiMock).when(zosProgramManagerSpy).getZosFile();
doReturn(artifactManagerMock).when(zosProgramManagerSpy).getArtifactManager();

allManagers = new ArrayList<>();
activeManagers = new ArrayList<>();
}



@After
public void teardown() throws Exception {
// Reset the singleton if it holds static state
Field f = ZosProgramPropertiesSingleton.class.getDeclaredField("singletonInstance");
f.setAccessible(true);
f.set(null, null);
}

// Tests


@Test
public void testCapitalisation_withRealAnnotatedField_throwsWhenLowercase() throws Exception {
Field realField = Dummy.class.getDeclaredField("myProgField");

ZosProgramManagerException ex = assertThrows(
ZosProgramManagerException.class,
() -> zosProgramManagerSpy.generateZosProgram(realField, Collections.emptyList())
);

//optional check of error message content
assertTrue(
"Expected error message to indicate non-capitalized program name",
ex.getMessage() != null && ex.getMessage().contains("not capitalized")
);

}


@Test
public void testValidLength_withRealAnnotatedField() throws Exception {
Field realField = Dummy.class.getDeclaredField("validField");
IZosProgram program = zosProgramManagerSpy.generateZosProgram(realField, Collections.emptyList());

assertNotNull(program);
assertEquals("MYPROG", program.getName());
}

@Test
public void testNameExactlyEightCharsIsValid_withRealAnnotatedField() throws Exception {
Field realField = Dummy.class.getDeclaredField("eightCharField");
IZosProgram program = zosProgramManagerSpy.generateZosProgram(realField, Collections.emptyList());

assertNotNull(program);
assertEquals("MYPROG01", program.getName());

}

@Test
public void testInvalidLengthThrowsException_withMockedAnnotation() throws Exception {

ZosProgramManagerImpl manager = spy(new ZosProgramManagerImpl());

Field mockField = mock(Field.class);
when(mockField.getName()).thenReturn("tooLongField");

ZosProgram annotation = mock(ZosProgram.class);
when(mockField.getAnnotation(ZosProgram.class)).thenReturn(annotation);
when(annotation.name()).thenReturn("TOOLONGNAME"); // > 8 chars
when(annotation.location()).thenReturn("source");
when(annotation.imageTag()).thenReturn("A");
when(annotation.language()).thenReturn(Language.COBOL);
when(annotation.cics()).thenReturn(false);
when(annotation.loadlib()).thenReturn("");
when(annotation.compile()).thenReturn(true);

// Sanity checks
ZosProgram resolved = mockField.getAnnotation(ZosProgram.class);
assertNotNull("Expected field.getAnnotation(ZosProgram.class) to return a non-null annotation", resolved);
assertEquals("TOOLONGNAME", resolved.name());
assertTrue("Precondition: name should be longer than 8 characters", resolved.name().length() > 8);

assertThrows(
ZosProgramManagerException.class,
() -> manager.generateZosProgram(mockField, Collections.emptyList())
);
}




}