/*
* Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
* Copyright (c) 2023 PANTHEON.tech, s.r.o.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.restconf.nb.jaxrs;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.doReturn;
import com.google.common.io.CharStreams;
import java.io.IOException;
import java.io.Reader;
import java.util.Optional;
import java.util.function.Consumer;
import javax.ws.rs.container.AsyncResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.opendaylight.mdsal.dom.api.DOMActionService;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService.YangTextSourceExtension;
import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
@ExtendWith(MockitoExtension.class)
class RestconfModulesGetTest extends AbstractRestconfTest {
private static final EffectiveModelContext MODEL_CONTEXT =
YangParserTestUtils.parseYangResourceDirectory("/parser-identifier");
private static final EffectiveModelContext MODEL_CONTEXT_ON_MOUNT_POINT =
YangParserTestUtils.parseYangResourceDirectory("/parser-identifier");
private static final String TEST_MODULE_NAME = "test-module";
private static final String TEST_MODULE_REVISION = "2016-06-02";
private static final String TEST_MODULE_NAMESPACE = "test:module";
private static final ApiPath MOUNT_POINT_IDENT = apiPath("mount-point:mount-container/point-number/yang-ext:mount");
private static final YangInstanceIdentifier MOUNT_IID = YangInstanceIdentifier.of(
QName.create("mount:point", "2016-06-02", "mount-container"),
QName.create("mount:point", "2016-06-02", "point-number"));
@Mock
private YangTextSourceExtension sourceProvider;
@Mock
private DOMSchemaService schemaService;
@Override
EffectiveModelContext modelContext() {
return MODEL_CONTEXT;
}
/**
* Positive test of getting SchemaExportContext
. Expected module name, revision and namespace are
* verified.
*/
@Test
void toSchemaExportContextFromIdentifierTest() {
assertEquals("""
module test-module {
namespace test:module;
prefix testm;
yang-version 1;
revision 2016-06-02 {
description
"Initial revision.";
}
}
""", assertYang(null, TEST_MODULE_NAME, TEST_MODULE_REVISION));
}
/**
* Test of getting SchemaExportContext
when desired module is not found.
* SchemaExportContext
should not be created and exception is thrown.
*/
@Test
void toSchemaExportContextFromIdentifierNotFoundTest() {
final var error = assertError(ar -> restconf.modulesYinGET("not-existing-module", "2016-01-01", ar));
assertEquals("Source not-existing-module@2016-01-01 not found", error.getErrorMessage());
assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
assertEquals(ErrorType.APPLICATION, error.getErrorType());
}
/**
* Negative test trying to get SchemaExportContext
with invalid identifier. Test is expected to fail
* with RestconfDocumentedException
error type, error tag and error status code are compared to
* expected values.
*/
@Test
void toSchemaExportContextFromIdentifierInvalidIdentifierNegativeTest() {
final var error = assertError(ar -> restconf.modulesYangGET(TEST_MODULE_REVISION, TEST_MODULE_NAME, ar));
assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
assertEquals(ErrorType.PROTOCOL, error.getErrorType());
assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
}
/**
* Positive test of getting SchemaExportContext
for module behind mount point.
* Expected module name, revision and namespace are verified.
*/
@Test
void toSchemaExportContextFromIdentifierMountPointTest() {
mockMountPoint();
final var content = assertYang(MOUNT_POINT_IDENT, TEST_MODULE_NAME, TEST_MODULE_REVISION);
assertEquals("""
module test-module {
namespace test:module;
prefix testm;
yang-version 1;
revision 2016-06-02 {
description
"Initial revision.";
}
}
""", content);
}
/**
* Negative test of getting SchemaExportContext
when desired module is not found behind mount point.
* SchemaExportContext
should not be created and exception is thrown.
*/
@Test
void toSchemaExportContextFromIdentifierMountPointNotFoundTest() {
mockMountPoint();
final var error = assertError(
ar -> restconf.modulesYangGET(MOUNT_POINT_IDENT, "not-existing-module", "2016-01-01", ar));
assertEquals("Source not-existing-module@2016-01-01 not found", error.getErrorMessage());
assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
assertEquals(ErrorType.APPLICATION, error.getErrorType());
}
/**
* Negative test trying to get SchemaExportContext
behind mount point with invalid identifier. Test is
* expected to fail with RestconfDocumentedException
error type, error tag and error status code are
* compared to expected values.
*/
@Test
void toSchemaExportContextFromIdentifierMountPointInvalidIdentifierNegativeTest() {
mockMountPoint();
final var error = assertError(
ar -> restconf.modulesYangGET(MOUNT_POINT_IDENT, TEST_MODULE_REVISION, TEST_MODULE_NAME, ar));
assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
assertEquals(ErrorType.PROTOCOL, error.getErrorType());
assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
}
@Test
void toSchemaExportContextFromIdentifierNullSchemaContextBehindMountPointNegativeTest() {
doReturn(Optional.of(schemaService)).when(mountPoint).getService(DOMSchemaService.class);
doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(MOUNT_IID);
final var error = assertError(
ar -> restconf.modulesYangGET(MOUNT_POINT_IDENT, TEST_MODULE_NAME, TEST_MODULE_REVISION, ar));
assertEquals("Mount point 'mount-point:mount-container/point-number' does not have any models",
error.getErrorMessage());
assertEquals(ErrorType.PROTOCOL, error.getErrorType());
assertEquals(ErrorTags.RESOURCE_DENIED_TRANSPORT, error.getErrorTag());
}
/**
* Positive test of getting SchemaExportContext
behind mount point for module without revision.
* Expected module name, prefix and namespace are verified.
*/
@Test
void toSchemaExportContextFromIdentifierNullRevisionTest() {
mockMountPoint();
final var content = assertYang(MOUNT_POINT_IDENT, "module-without-revision", null);
assertEquals("""
module module-without-revision {
namespace module:without:revision;
prefix mwr;
}
""", content);
}
private void mockMountPoint() {
doReturn(Optional.of(new FixedDOMSchemaService(MODEL_CONTEXT_ON_MOUNT_POINT))).when(mountPoint)
.getService(DOMSchemaService.class);
doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(MOUNT_IID);
doReturn(Optional.of(mountPointService)).when(mountPoint).getService(DOMMountPointService.class);
doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
}
/**
* Positive test of getting SchemaExportContext
for module without revision.
* Expected module name, prefix and namespace are verified.
*/
@Test
void validateAndGetModuleWithoutRevisionTest() {
final var content = assertYang(null, "module-without-revision", null);
assertEquals("""
module module-without-revision {
namespace module:without:revision;
prefix mwr;
}
""", content);
}
/**
* Negative test of module revision validation when supplied revision is not parsable as revision. Test fails
* catching RestconfDocumentedException
.
*/
@Test
void validateAndGetRevisionNotParsableTest() {
final var error = assertInvalidValue("module", "not-parsable-as-date");
assertEquals("Supplied revision is not in expected date format YYYY-mm-dd", error.getErrorMessage());
}
/**
* Negative test of module name validation when there is no module name. Test fails catching
* RestconfDocumentedException
and checking for correct error type, error tag and error status code.
*/
@Test
void validateAndGetModulNameNotSuppliedTest() {
final var error = assertInvalidValue(null, null);
assertEquals("Module name must be supplied", error.getErrorMessage());
}
/**
* Negative test of module name validation when supplied name is not parsable as module name on the first
* character. Test fails catching RestconfDocumentedException
and checking for correct error type,
* error tag and error status code.
*/
@Test
void validateAndGetModuleNameNotParsableFirstTest() {
final var error = assertInvalidValue("01-not-parsable-as-name-on-first-char", null);
assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
}
/**
* Negative test of module name validation when supplied name is not parsable as module name on any of the
* characters after the first character. Test fails catching RestconfDocumentedException
and checking
* for correct error type, error tag and error status code.
*/
@Test
public void validateAndGetModuleNameNotParsableNextTest() {
final var error = assertInvalidValue("not-parsable-as-name-after-first-char*", null);
assertEquals("Supplied name has not expected identifier format", error.getErrorMessage());
}
/**
* Negative test of module name validation when supplied name is empty. Test fails catching
* RestconfDocumentedException
and checking for correct error type, error tag and error status code.
*/
@Test
void validateAndGetModuleNameEmptyTest() {
final var error = assertInvalidValue("", null);
assertEquals("Identifier must start with character from set 'a-zA-Z_", error.getErrorMessage());
}
private String assertYang(final ApiPath mountPath, final String fileName, final String revision) {
final Consumer invocation = mountPath != null
? ar -> restconf.modulesYangGET(mountPath, fileName, revision, ar)
: ar -> restconf.modulesYangGET(fileName, revision, ar);
try (var reader = assertEntity(Reader.class, 200, invocation)) {
return CharStreams.toString(reader);
} catch (IOException e) {
throw new AssertionError(e);
}
}
private RestconfError assertInvalidValue(final String fileName, final String revision) {
final var error = assertError(ar -> restconf.modulesYangGET(fileName, revision, ar));
assertEquals(ErrorType.PROTOCOL, error.getErrorType());
assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
return error;
}
}