4.3 KiB · text History 53f3a6c
defmodule GitGud.Repositories.LicenseTest do
  @moduledoc """
  Recognising a licence from the file a repository keeps it in.

  The AGPL case reads this repository's own `LICENSE.md`, so the
  detector is tested against a real 34 KB licence rather than against a
  snippet written to match it. The rest are the distinguishing lines of
  each licence, which is all the detector claims to read.

  The negative cases matter as much: this ends up in a package index,
  where a wrong licence is worse than no licence.
  """
  use ExUnit.Case, async: true

  alias GitGud.Repositories.License

  @mit """
  MIT License

  Copyright (c) 2026 Someone

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
  """

  @bsd3 """
  Copyright (c) 2026, Someone

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

  3. Neither the name of the copyright holder nor the names of its
     contributors may be used to endorse or promote products.
  """

  describe "detect/1" do
    test "reads this repository's own AGPL" do
      assert License.detect(File.read!("LICENSE.md")) == "AGPL-3.0"
    end

    test "tells the GNU family apart" do
      assert License.detect("GNU AFFERO GENERAL PUBLIC LICENSE\nVersion 3, 19 November 2007") ==
               "AGPL-3.0"

      assert License.detect("GNU LESSER GENERAL PUBLIC LICENSE\nVersion 3, 29 June 2007") ==
               "LGPL-3.0"

      assert License.detect("GNU GENERAL PUBLIC LICENSE\nVersion 3, 29 June 2007") == "GPL-3.0"
      assert License.detect("GNU GENERAL PUBLIC LICENSE\nVersion 2, June 1991") == "GPL-2.0"
    end

    test "reads the permissive ones" do
      assert License.detect(@mit) == "MIT"
      assert License.detect(@bsd3) == "BSD-3-Clause"
      assert License.detect("Apache License\nVersion 2.0, January 2004") == "Apache-2.0"

      assert License.detect(
               "Permission to use, copy, modify, and/or distribute this software for any purpose"
             ) == "ISC"
    end

    test "an SPDX identifier in the file wins outright" do
      # The author saying exactly what they meant, including the
      # `-or-later` the licence text itself can't tell you.
      assert License.detect("SPDX-License-Identifier: AGPL-3.0-or-later\n") == "AGPL-3.0-or-later"
      assert License.detect("GNU GENERAL PUBLIC LICENSE\nSPDX-License-Identifier: MIT") == "MIT"
    end

    test "doesn't mistake a mention for a licence" do
      assert License.detect("We are thinking about the GNU General Public License.") == nil
      assert License.detect("Copyright (c) 2026 Someone. All rights reserved.") == nil
      assert License.detect("") == nil
    end

    test "survives line wrapping at any width" do
      wrapped =
        "Permission is hereby granted,\nfree of charge, to any person.\n" <>
          "THE SOFTWARE IS PROVIDED\n\"AS IS\", WITHOUT WARRANTY"

      assert License.detect(wrapped) == "MIT"
    end
  end

  describe "candidate?/1" do
    test "matches the files a licence lives in" do
      for name <- ~w(LICENSE LICENSE.md licence.txt COPYING COPYING.LESSER UNLICENSE LICENSE-MIT) do
        assert License.candidate?(name), "#{name} should be a candidate"
      end
    end

    test "is a filename predicate, not a path one" do
      # A source file that happens to be called that is none of its
      # business; only the root tree is ever scanned.
      refute License.candidate?("src/license.rs")
      refute License.candidate?("README.md")
      refute License.candidate?("licensing-policy.md")
    end
  end

  describe "detect_all/1" do
    test "joins a dual licence the way SPDX spells it" do
      files = [{"LICENSE-APACHE", "Apache License\nVersion 2.0"}, {"LICENSE-MIT", @mit}]

      assert License.detect_all(files) == "Apache-2.0 OR MIT"
    end

    test "collapses copies of the same licence" do
      files = [{"LICENSE", @mit}, {"COPYING", @mit}]

      assert License.detect_all(files) == "MIT"
    end

    test "ignores files that aren't licences, and unreadable ones" do
      assert License.detect_all([{"README.md", @mit}]) == nil
      assert License.detect_all([{"LICENSE", "some licence we've never seen"}]) == nil
      assert License.detect_all([]) == nil
    end
  end
end