Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:
runs-on: windows-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
6.0.x
Expand All @@ -27,7 +27,7 @@ jobs:
run: dotnet build --configuration Release --no-restore

- name: Upload build artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: packages
path: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ jobs:
runs-on: windows-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
6.0.x
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:
runs-on: windows-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
6.0.x
Expand Down
18 changes: 13 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.0.0] - 2025-01-XX
## [1.0.0] - 2025-12-15

### Added

- `PluginStepAttribute` for declarative plugin step registration
- `Message`, `EntityLogicalName`, `Stage` (required)
- `Mode`, `FilteringAttributes`, `ExecutionOrder` (optional)
- `UnsecureConfiguration`, `SecureConfiguration` for plugin settings
- `StepId` for multi-step plugins
- `PluginImageAttribute` for defining pre/post images
- `PluginStage` enum (PreValidation, PreOperation, PostOperation)
- `PluginMode` enum (Synchronous, Asynchronous)
- `PluginImageType` enum (PreImage, PostImage, Both)
- Multi-targeting: net462, net6.0, net8.0
- `ImageType`, `Name` (required)
- `Attributes`, `EntityAlias`, `StepId` (optional)
- `PluginStage` enum (`PreValidation`, `PreOperation`, `PostOperation`)
- `PluginMode` enum (`Synchronous`, `Asynchronous`)
- `PluginImageType` enum (`PreImage`, `PostImage`, `Both`)
- Multi-targeting: `net462`, `net6.0`, `net8.0`
- Strong name signing for Dataverse compatibility
- Full XML documentation
- GitHub Actions workflows for build and NuGet publishing
- Comprehensive unit test suite

[Unreleased]: https://github.com/joshsmithxrm/ppds-sdk/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/joshsmithxrm/ppds-sdk/releases/tag/v1.0.0
20 changes: 20 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,23 @@ Extracted by:
| `PPDS.Plugins.snk` | Strong name key (DO NOT regenerate) |
| `CHANGELOG.md` | Release notes |
| `.editorconfig` | Code style settings |

---

## Testing Requirements

- **Target 80% code coverage.** Tests must pass before PR.
- Unit tests for all public API (attributes, enums)
- Run `dotnet test` before submitting PR

---

## Decision Presentation

When presenting choices or asking questions:
1. **Lead with your recommendation** and rationale
2. **List alternatives considered** and why they're not preferred
3. **Ask for confirmation**, not open-ended input

❌ "What testing approach should we use?"
✅ "I recommend X because Y. Alternatives considered: A (rejected because B), C (rejected because D). Do you agree?"
17 changes: 17 additions & 0 deletions PPDS.Sdk.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PPDS.Plugins", "src\PPDS.Plugins\PPDS.Plugins.csproj", "{1E79DC81-59E1-4E4F-8B73-7F05E99F03F4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PPDS.Plugins.Tests", "tests\PPDS.Plugins.Tests\PPDS.Plugins.Tests.csproj", "{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -29,11 +33,24 @@ Global
{1E79DC81-59E1-4E4F-8B73-7F05E99F03F4}.Release|x64.Build.0 = Release|Any CPU
{1E79DC81-59E1-4E4F-8B73-7F05E99F03F4}.Release|x86.ActiveCfg = Release|Any CPU
{1E79DC81-59E1-4E4F-8B73-7F05E99F03F4}.Release|x86.Build.0 = Release|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Debug|x64.ActiveCfg = Debug|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Debug|x64.Build.0 = Debug|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Debug|x86.ActiveCfg = Debug|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Debug|x86.Build.0 = Debug|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Release|Any CPU.Build.0 = Release|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Release|x64.ActiveCfg = Release|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Release|x64.Build.0 = Release|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Release|x86.ActiveCfg = Release|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{1E79DC81-59E1-4E4F-8B73-7F05E99F03F4} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
EndGlobalSection
EndGlobal
9 changes: 9 additions & 0 deletions src/PPDS.Plugins/Attributes/PluginStepAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,18 @@ public sealed class PluginStepAttribute : Attribute

/// <summary>
/// Gets or sets the unsecure configuration string passed to the plugin constructor.
/// This configuration is stored in plain text and is visible to users with appropriate read access
/// to the plugin step entity, as determined by their security roles and privileges in Dataverse.
/// </summary>
public string? UnsecureConfiguration { get; set; }

/// <summary>
/// Gets or sets the secure configuration string passed to the plugin constructor.
/// This configuration is encrypted and only accessible by the plugin at runtime.
/// Use for sensitive data like API keys or connection strings.
/// </summary>
public string? SecureConfiguration { get; set; }

/// <summary>
/// Gets or sets a unique identifier for this step when a plugin has multiple steps.
/// Used to associate PluginImageAttribute with a specific step.
Expand Down
94 changes: 94 additions & 0 deletions tests/PPDS.Plugins.Tests/EnumTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using Xunit;

namespace PPDS.Plugins.Tests;

public class EnumTests
{
#region PluginStage Tests

[Theory]
[InlineData(PluginStage.PreValidation, 10)]
[InlineData(PluginStage.PreOperation, 20)]
[InlineData(PluginStage.PostOperation, 40)]
public void PluginStage_ValuesMatchDataverseSDK(PluginStage stage, int expectedValue)
{
// These values must match the Dataverse SDK for proper registration
Assert.Equal(expectedValue, (int)stage);
}

[Fact]
public void PluginStage_HasExactlyThreeValues()
{
var values = Enum.GetValues<PluginStage>();
Assert.Equal(3, values.Length);
}

#endregion

#region PluginMode Tests

[Theory]
[InlineData(PluginMode.Synchronous, 0)]
[InlineData(PluginMode.Asynchronous, 1)]
public void PluginMode_ValuesMatchDataverseSDK(PluginMode mode, int expectedValue)
{
// These values must match the Dataverse SDK for proper registration
Assert.Equal(expectedValue, (int)mode);
}

[Fact]
public void PluginMode_HasExactlyTwoValues()
{
var values = Enum.GetValues<PluginMode>();
Assert.Equal(2, values.Length);
}

#endregion

#region PluginImageType Tests

[Theory]
[InlineData(PluginImageType.PreImage, 0)]
[InlineData(PluginImageType.PostImage, 1)]
[InlineData(PluginImageType.Both, 2)]
public void PluginImageType_ValuesMatchDataverseSDK(PluginImageType imageType, int expectedValue)
{
// These values must match the Dataverse SDK for proper registration
Assert.Equal(expectedValue, (int)imageType);
}

[Fact]
public void PluginImageType_HasExactlyThreeValues()
{
var values = Enum.GetValues<PluginImageType>();
Assert.Equal(3, values.Length);
}

#endregion

#region Cross-Enum Tests

[Fact]
public void AllEnums_AreInCorrectNamespace()
{
Assert.Equal("PPDS.Plugins", typeof(PluginStage).Namespace);
Assert.Equal("PPDS.Plugins", typeof(PluginMode).Namespace);
Assert.Equal("PPDS.Plugins", typeof(PluginImageType).Namespace);
}

[Theory]
[InlineData(typeof(PluginStage))]
[InlineData(typeof(PluginMode))]
[InlineData(typeof(PluginImageType))]
public void AllEnums_AreValidAndNotEmpty(Type enumType)
{
// Verify the enum type exists and is an enum
Assert.True(enumType.IsEnum);

// Verify all values exist (will throw if values are missing)
var values = Enum.GetValues(enumType);
Assert.True(values.Length > 0);
}

#endregion
}
29 changes: 29 additions & 0 deletions tests/PPDS.Plugins.Tests/PPDS.Plugins.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>PPDS.Plugins.Tests</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.4" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\PPDS.Plugins\PPDS.Plugins.csproj" />
</ItemGroup>

</Project>
Loading
Loading