Skip to content

fix(license): treat UNKNOWN-category dependencies as incompatible#513

Open
a-oren wants to merge 1 commit into
guacsec:mainfrom
a-oren:TC-4707
Open

fix(license): treat UNKNOWN-category dependencies as incompatible#513
a-oren wants to merge 1 commit into
guacsec:mainfrom
a-oren:TC-4707

Conversation

@a-oren
Copy link
Copy Markdown
Contributor

@a-oren a-oren commented Jun 4, 2026

Summary

  • Split the UNKNOWN category check in getCompatibility() so dependencies with unrecognized licenses are flagged as incompatible when the project has a known license category
  • Added distinct reason message for UNKNOWN-category dependencies: "License not recognized as a standard SPDX identifier. Manual review recommended to verify compatibility."
  • Added parameterized tests covering all UNKNOWN/known category combinations and preserving existing behavior

Implements TC-4707

Test plan

  • getCompatibility(PERMISSIVE|WEAK_COPYLEFT|STRONG_COPYLEFT, UNKNOWN) returns INCOMPATIBLE
  • getCompatibility(UNKNOWN, *) returns UNKNOWN (no false positives)
  • getCompatibility(null, *) returns UNKNOWN
  • Known category pairs preserve existing behavior
  • All 454 existing tests pass (6 pre-existing Python env failures unrelated)

Summary by Sourcery

Treat dependencies with UNKNOWN license category as incompatible when the project license category is known, and clarify reporting for such cases.

Bug Fixes:

  • Ensure UNKNOWN-category dependency licenses are reported as incompatible when the project has a known license category.

Enhancements:

  • Add a specific incompatibility reason message for dependencies with UNKNOWN license category in license check results.

Tests:

  • Introduce parameterized tests covering UNKNOWN vs known license category combinations and verifying existing compatibility behavior for known categories.

Split the UNKNOWN category check in getCompatibility() so that
dependencies with unrecognized licenses are flagged as incompatible
when the project has a known license category, while keeping UNKNOWN
project category returning UNKNOWN (no false positives). Add a distinct
reason message for UNKNOWN-category dependencies to guide manual review.

Implements TC-4707

Assisted-by: Claude Code
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Jun 4, 2026

Reviewer's Guide

Adjusts license compatibility logic so UNKNOWN dependency categories are treated as incompatible when the project license category is known, adds a specific user-facing reason message for these cases, and introduces parameterized tests to lock in behavior for UNKNOWN and known category combinations.

Sequence diagram for handling UNKNOWN dependency license category

sequenceDiagram
  participant LicenseCheck
  participant LicenseUtils
  participant IncompatibleDependency

  LicenseCheck->>LicenseUtils: getCompatibility(projectCategory, entry.category)
  LicenseUtils-->>LicenseCheck: Compatibility.INCOMPATIBLE
  alt entry.category == LicenseCategory.UNKNOWN
    LicenseCheck->>IncompatibleDependency: new IncompatibleDependency(purl, entry.licenses, entry.category, reason)
    Note right of IncompatibleDependency: License not recognized as a standard SPDX identifier. Manual review recommended to verify compatibility.
  else entry.category != LicenseCategory.UNKNOWN
    LicenseCheck->>IncompatibleDependency: new IncompatibleDependency(purl, entry.licenses, entry.category, reason)
    Note right of IncompatibleDependency: Dependency license(s) are incompatible with the project license.
  end
Loading

Flow diagram for updated license compatibility evaluation

flowchart TD
  A[Start getCompatibility
  projectCategory, dependencyCategory] --> B{projectCategory == null
  or dependencyCategory == null}
  B -->|yes| C[Return Compatibility.UNKNOWN]
  B -->|no| D{projectCategory == LicenseCategory.UNKNOWN}
  D -->|yes| C
  D -->|no| E{dependencyCategory == LicenseCategory.UNKNOWN}
  E -->|yes| F[Return Compatibility.INCOMPATIBLE]
  E -->|no| G[Compute restrictiveness
  for projectCategory and dependencyCategory]
  G --> H[Return Compatibility based on
  restrictiveness comparison]
Loading

File-Level Changes

Change Details Files
Treat UNKNOWN dependency license category as incompatible when project category is known, and surface a tailored reason message.
  • Refined getCompatibility() to return UNKNOWN only when the project category is UNKNOWN (or null) and INCOMPATIBLE when the dependency category is UNKNOWN while the project category is known.
  • Preserved existing comparison-based compatibility logic for known project/dependency category pairs after the UNKNOWN handling short-circuit.
  • Adjusted LicenseCheck.runLicenseCheck() to construct a specialized reason string for UNKNOWN-category dependencies, while keeping the existing generic incompatibility message for all other incompatible cases.
src/main/java/io/github/guacsec/trustifyda/license/LicenseUtils.java
src/main/java/io/github/guacsec/trustifyda/license/LicenseCheck.java
Add focused, parameterized tests to pin behavior of UNKNOWN and known license category combinations without regressing current compatibility rules.
  • Introduced License_Compatibility_Test with nested test classes separating UNKNOWN dependency, UNKNOWN/null project, and known-category scenarios.
  • Added parameterized tests ensuring known project + UNKNOWN dependency yields INCOMPATIBLE, UNKNOWN project yields UNKNOWN for all dependency categories, and null project yields UNKNOWN for all dependency categories.
  • Defined a knownCategoryPairs() MethodSource enumerating representative known-category combinations to verify existing compatibility semantics for equal, less restrictive, and more restrictive dependency categories remain unchanged.
src/test/java/io/github/guacsec/trustifyda/license/License_Compatibility_Test.java

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • Consider centralizing the construction of the incompatibility reason message (e.g., in LicenseUtils or a small helper) so that future callers of getCompatibility don’t have to re‑encode the UNKNOWN vs non‑UNKNOWN branching logic and texts independently.
  • If LicenseCategory.UNKNOWN can be produced for reasons other than a non‑SPDX identifier, you may want to either adjust the wording of the new reason string or have the code receive a more specific cause from the classification layer to avoid over‑specifying the root cause here.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider centralizing the construction of the incompatibility reason message (e.g., in `LicenseUtils` or a small helper) so that future callers of `getCompatibility` don’t have to re‑encode the UNKNOWN vs non‑UNKNOWN branching logic and texts independently.
- If `LicenseCategory.UNKNOWN` can be produced for reasons other than a non‑SPDX identifier, you may want to either adjust the wording of the new reason string or have the code receive a more specific cause from the classification layer to avoid over‑specifying the root cause here.

## Individual Comments

### Comment 1
<location path="src/test/java/io/github/guacsec/trustifyda/license/License_Compatibility_Test.java" line_range="67-64" />
<code_context>
+      assertThat(result).isEqualTo(Compatibility.UNKNOWN);
+    }
+
+    /** Null project category should return UNKNOWN. */
+    @ParameterizedTest
+    @EnumSource(LicenseCategory.class)
+    void null_project_returns_unknown(LicenseCategory dependencyCategory) {
+      // When
+      Compatibility result = LicenseUtils.getCompatibility(null, dependencyCategory);
+
+      // Then
+      assertThat(result).isEqualTo(Compatibility.UNKNOWN);
+    }
+  }
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for null dependency category to fully cover the null-handling branch

The implementation returns `Compatibility.UNKNOWN` when either `projectCategory` or `dependencyCategory` is `null`, but tests only cover the `null` project case. Please also add parameterized tests for:

- `getCompatibility(projectCategory, null)` for each non-null project category (including `UNKNOWN`).
- `getCompatibility(null, null)` explicitly.

This will fully cover the null-handling branch and protect against regressions.

Suggested implementation:

```java
    /** Null project category should return UNKNOWN. */
    @ParameterizedTest
    @EnumSource(LicenseCategory.class)
    void null_project_returns_unknown(LicenseCategory dependencyCategory) {
      // When
      Compatibility result = LicenseUtils.getCompatibility(null, dependencyCategory);

      // Then
      assertThat(result).isEqualTo(Compatibility.UNKNOWN);
    }

    /** Null dependency category should return UNKNOWN. */
    @ParameterizedTest
    @EnumSource(LicenseCategory.class)
    void null_dependency_returns_unknown(LicenseCategory projectCategory) {
      // When
      Compatibility result = LicenseUtils.getCompatibility(projectCategory, null);

      // Then
      assertThat(result).isEqualTo(Compatibility.UNKNOWN);
    }

    /** Null project and dependency categories should return UNKNOWN. */
    @Test
    void null_project_and_dependency_return_unknown() {
      // When
      Compatibility result = LicenseUtils.getCompatibility(null, null);

      // Then
      assertThat(result).isEqualTo(Compatibility.UNKNOWN);
    }
  }

```

If `@Test` is not already imported in this test class, add:

`import org.junit.jupiter.api.Test;`

near the other JUnit imports.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

LicenseUtils.getCompatibility(LicenseCategory.UNKNOWN, dependencyCategory);

// Then
assertThat(result).isEqualTo(Compatibility.UNKNOWN);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add tests for null dependency category to fully cover the null-handling branch

The implementation returns Compatibility.UNKNOWN when either projectCategory or dependencyCategory is null, but tests only cover the null project case. Please also add parameterized tests for:

  • getCompatibility(projectCategory, null) for each non-null project category (including UNKNOWN).
  • getCompatibility(null, null) explicitly.

This will fully cover the null-handling branch and protect against regressions.

Suggested implementation:

    /** Null project category should return UNKNOWN. */
    @ParameterizedTest
    @EnumSource(LicenseCategory.class)
    void null_project_returns_unknown(LicenseCategory dependencyCategory) {
      // When
      Compatibility result = LicenseUtils.getCompatibility(null, dependencyCategory);

      // Then
      assertThat(result).isEqualTo(Compatibility.UNKNOWN);
    }

    /** Null dependency category should return UNKNOWN. */
    @ParameterizedTest
    @EnumSource(LicenseCategory.class)
    void null_dependency_returns_unknown(LicenseCategory projectCategory) {
      // When
      Compatibility result = LicenseUtils.getCompatibility(projectCategory, null);

      // Then
      assertThat(result).isEqualTo(Compatibility.UNKNOWN);
    }

    /** Null project and dependency categories should return UNKNOWN. */
    @Test
    void null_project_and_dependency_return_unknown() {
      // When
      Compatibility result = LicenseUtils.getCompatibility(null, null);

      // Then
      assertThat(result).isEqualTo(Compatibility.UNKNOWN);
    }
  }

If @Test is not already imported in this test class, add:

import org.junit.jupiter.api.Test;

near the other JUnit imports.

@a-oren a-oren requested review from Strum355 and removed request for Strum355 June 4, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant