Skip to content
Draft
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
4 changes: 3 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
* Make only high confidence suggestions when reviewing code changes.
* Never change NuGet.config files unless explicitly asked to.

## Formatting
## Formatting and Style

* Apply code-formatting style defined in `.editorconfig`.
* Prefer file-scoped namespace declarations and single-line using directives.
* Insert a newline before the opening curly brace of any code block (e.g., after `if`, `for`, `while`, `foreach`, `using`, `try`, etc.).
* Ensure that the final return statement of a method is on its own line.
* Use pattern matching and switch expressions wherever possible.
* Use `nameof` instead of string literals when referring to member names.
* Delete unused `using` directives

### Nullable Reference Types

Expand All @@ -27,4 +28,5 @@

## Running tests

* Copilot should build and run unit tests after making changes
* To build and run tests in the repo, use the command `./build.ps1 UnitTest`
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
// SPDX-License-Identifier: Apache-2.0
// Licensed to the Ed-Fi Alliance under one or more agreements.
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System.Collections.Generic;
using System.Threading.Tasks;
using AutoMapper;
using EdFi.Admin.DataAccess.Models;
using EdFi.Ods.AdminApi.Common.Infrastructure;
using EdFi.Ods.AdminApi.Common.Infrastructure.ErrorHandling;
using EdFi.Ods.AdminApi.Features.OdsInstanceContext;
using EdFi.Ods.AdminApi.Infrastructure.Database.Commands;
using EdFi.Ods.AdminApi.Infrastructure.Database.Queries;
using FakeItEasy;
using FluentValidation;
using Microsoft.AspNetCore.Http;
using NUnit.Framework;
using Shouldly;
using OdsInstanceContextEntity = EdFi.Admin.DataAccess.Models.OdsInstanceContext;

namespace EdFi.Ods.AdminApi.UnitTests.Features.OdsInstanceContext;

[TestFixture]
public class AddOdsInstanceContextTests
{
private AddOdsInstanceContext.Validator _validator;
private IGetOdsInstanceQuery _getOdsInstanceQuery;
private IGetOdsInstanceContextsQuery _getOdsInstanceContextsQuery;

[SetUp]
public void SetUp()
{
_getOdsInstanceQuery = A.Fake<IGetOdsInstanceQuery>();
_getOdsInstanceContextsQuery = A.Fake<IGetOdsInstanceContextsQuery>();
_validator = new AddOdsInstanceContext.Validator(_getOdsInstanceQuery, _getOdsInstanceContextsQuery);
}

[Test]
public async Task Handle_ExecutesCommandAndReturnsCreated()
{
// Arrange

var command = A.Fake<IAddOdsInstanceContextCommand>();
var mapper = A.Fake<IMapper>();
var request = new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
OdsInstanceId = 1,
ContextKey = "TestKey",
ContextValue = "TestValue"
};
var addedContext = new OdsInstanceContextEntity { OdsInstanceContextId = 123 };

A.CallTo(() => command.Execute(request)).Returns(addedContext);

// Act
var result = await AddOdsInstanceContext.Handle(_validator, command, mapper, request);

// Assert
A.CallTo(() => command.Execute(request)).MustHaveHappenedOnceExactly();
result.ShouldNotBeNull();
result.ShouldBeOfType<Microsoft.AspNetCore.Http.HttpResults.Created>();
}

[Test]
public void Handle_WhenValidationFails_ThrowsValidationException()
{
// Arrange

var command = A.Fake<IAddOdsInstanceContextCommand>();
var mapper = A.Fake<IMapper>();
var request = new AddOdsInstanceContext.AddOdsInstanceContextRequest();

// Act & Assert
Should.Throw<ValidationException>(
async () => await AddOdsInstanceContext.Handle(_validator, command, mapper, request)
);
}

[Test]
public void Handle_WhenCommandThrows_ExceptionIsPropagated()
{
// Arrange

var command = A.Fake<IAddOdsInstanceContextCommand>();
var mapper = A.Fake<IMapper>();
var request = new AddOdsInstanceContext.AddOdsInstanceContextRequest();

A.CallTo(() => command.Execute(request)).Throws(new System.Exception("Command failed"));

// Act & Assert
Should.Throw<System.Exception>(
async () => await AddOdsInstanceContext.Handle(_validator, command, mapper, request)
);
}

[Test]
public void Validator_Should_Have_Error_When_ContextKey_Is_Empty()
{
// Arrange
var model = new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
ContextKey = "",
ContextValue = "TestValue",
OdsInstanceId = 1
};

// Act
var result = _validator.Validate(model);

// Assert
result.IsValid.ShouldBeFalse();
result.Errors.ShouldContain(x => x.PropertyName == nameof(model.ContextKey));
}

[Test]
public void Validator_Should_Have_Error_When_ContextKey_Is_Null()
{
// Arrange
var model = new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
ContextKey = null,
ContextValue = "TestValue",
OdsInstanceId = 1
};

// Act
var result = _validator.Validate(model);

// Assert
result.IsValid.ShouldBeFalse();
result.Errors.ShouldContain(x => x.PropertyName == nameof(model.ContextKey));
}

[Test]
public void Validator_Should_Have_Error_When_ContextValue_Is_Empty()
{
// Arrange
var model = new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
ContextKey = "TestKey",
ContextValue = "",
OdsInstanceId = 1
};

// Act
var result = _validator.Validate(model);

// Assert
result.IsValid.ShouldBeFalse();
result.Errors.ShouldContain(x => x.PropertyName == nameof(model.ContextValue));
}

[Test]
public void Validator_Should_Have_Error_When_ContextValue_Is_Null()
{
// Arrange
var model = new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
ContextKey = "TestKey",
ContextValue = null,
OdsInstanceId = 1
};

// Act
var result = _validator.Validate(model);

// Assert
result.IsValid.ShouldBeFalse();
result.Errors.ShouldContain(x => x.PropertyName == nameof(model.ContextValue));
}

[Test]
public void Validator_Should_Have_Error_When_OdsInstanceId_Is_Zero()
{
// Arrange
var model = new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
ContextKey = "TestKey",
ContextValue = "TestValue",
OdsInstanceId = 0
};

// Act
var result = _validator.Validate(model);

// Assert
result.IsValid.ShouldBeFalse();
result.Errors.ShouldContain(x => x.PropertyName == nameof(model.OdsInstanceId));
}

[Test]
public void Validator_Should_Have_Error_When_OdsInstance_Does_Not_Exist()
{
// Arrange
var model = new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
ContextKey = "TestKey",
ContextValue = "TestValue",
OdsInstanceId = 999
};

A.CallTo(() => _getOdsInstanceQuery.Execute(999))
.Throws(new NotFoundException<int>("odsInstance", 999));

// Act
var result = _validator.Validate(model);

// Assert
result.IsValid.ShouldBeFalse();
result.Errors.ShouldContain(x => x.PropertyName == nameof(model.OdsInstanceId));
}

[Test]
public void Validator_Should_Have_Error_When_Combined_Key_Is_Not_Unique()
{
// Arrange
var model = new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
ContextKey = "ExistingKey",
ContextValue = "TestValue",
OdsInstanceId = 1
};

var existingContexts = new List<OdsInstanceContextEntity>
{
new OdsInstanceContextEntity
{
ContextKey = "ExistingKey",
OdsInstance = new OdsInstance { OdsInstanceId = 1 }
}
};

A.CallTo(() => _getOdsInstanceContextsQuery.Execute()).Returns(existingContexts);

// Act
var result = _validator.Validate(model);

// Assert
result.IsValid.ShouldBeFalse();
result.Errors.ShouldContain(x => x.PropertyName == "");
}

[Test]
public void Validator_Should_Pass_When_Combined_Key_Is_Unique()
{
// Arrange
var model = new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
ContextKey = "UniqueKey",
ContextValue = "TestValue",
OdsInstanceId = 1
};

var existingContexts = new List<OdsInstanceContextEntity>
{
new OdsInstanceContextEntity
{
ContextKey = "DifferentKey",
OdsInstance = new OdsInstance { OdsInstanceId = 1 }
}
};

A.CallTo(() => _getOdsInstanceContextsQuery.Execute()).Returns(existingContexts);

// Act
var result = _validator.Validate(model);

// Assert
result.IsValid.ShouldBeTrue();
}

[Test]
public void Validator_Should_Pass_With_Valid_Model()
{
// Arrange
var model = new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
ContextKey = "ValidKey",
ContextValue = "ValidValue",
OdsInstanceId = 1
};

A.CallTo(() => _getOdsInstanceContextsQuery.Execute()).Returns(new List<OdsInstanceContextEntity>());

// Act
var result = _validator.Validate(model);

// Assert
result.IsValid.ShouldBeTrue();
}

[Test]
public void BeAnExistingOdsInstance_Should_Return_True_When_OdsInstance_Exists()
{
// Arrange
var odsInstanceId = 1;
A.CallTo(() => _getOdsInstanceQuery.Execute(odsInstanceId)).Returns(new OdsInstance());

// Act
var result = _validator.Validate(
new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
ContextKey = "TestKey",
ContextValue = "TestValue",
OdsInstanceId = odsInstanceId
}
);

// Assert - The validation should pass (assuming other fields are valid)
A.CallTo(() => _getOdsInstanceQuery.Execute(odsInstanceId)).MustHaveHappenedOnceExactly();
}

[Test]
public void BeUniqueCombinedKey_Should_Return_True_When_Key_Is_Case_Insensitive_Unique()
{
// Arrange
var model = new AddOdsInstanceContext.AddOdsInstanceContextRequest
{
ContextKey = "TESTKEY",
ContextValue = "TestValue",
OdsInstanceId = 1
};

var existingContexts = new List<OdsInstanceContextEntity>
{
new OdsInstanceContextEntity
{
ContextKey = "testkey", // different case
OdsInstance = new OdsInstance { OdsInstanceId = 2 } // different instance
}
};

A.CallTo(() => _getOdsInstanceContextsQuery.Execute()).Returns(existingContexts);

// Act
var result = _validator.Validate(model);

// Assert
result.IsValid.ShouldBeTrue();
}
}
Loading
Loading