diff --git a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp
index a6a1dee6d4..7d8df61ffd 100644
--- a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp
+++ b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp
@@ -52,6 +52,18 @@ namespace AppInstaller::CLI::Workflow
std::string_view arg = context.Args.GetArg(Args::Type::SourceArg);
std::string_view type = context.Args.GetArg(Args::Type::SourceType);
+ // In the absence of a specified type, the default is Microsoft.PreIndexed.Package for comparison.
+ // The default type assignment to the source takes place during the add operation (Source::Add in Repository.cpp).
+ // This is necessary for the comparison to function correctly; otherwise, it would allow the addition of multiple
+ // sources with different names but the same argument for all default type cases.
+ // For example, the following commands would be allowed, but they acts as different alias to same source:
+ // winget source add "mysource1" "https:\\mysource" --trust - level trusted
+ // winget source add "mysource2" "https:\\mysource" --trust - level trusted
+ if (type.empty())
+ {
+ type = Repository::Source::GetDefaultSourceType();
+ }
+
for (const auto& details : sourceList)
{
if (Utility::ICUCaseInsensitiveEquals(details.Name, name))
diff --git a/src/AppInstallerCLIE2ETests/SourceCommand.cs b/src/AppInstallerCLIE2ETests/SourceCommand.cs
index 27ee083e13..c873a6d72b 100644
--- a/src/AppInstallerCLIE2ETests/SourceCommand.cs
+++ b/src/AppInstallerCLIE2ETests/SourceCommand.cs
@@ -45,6 +45,9 @@ public void SourceAdd()
[Test]
public void SourceAddWithTrustLevel()
{
+ // Remove the test source.
+ TestCommon.RunAICLICommand("source remove", Constants.TestSourceName);
+
var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level trusted");
Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
Assert.True(result.StdOut.Contains("Done"));
@@ -62,6 +65,9 @@ public void SourceAddWithTrustLevel()
[Test]
public void SourceAddWithStoreOriginTrustLevel()
{
+ // Remove the test source.
+ TestCommon.RunAICLICommand("source remove", Constants.TestSourceName);
+
var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level storeOrigin");
Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_DATA_INTEGRITY_FAILURE, result.ExitCode);
Assert.True(result.StdOut.Contains("The source data is corrupted or tampered"));
@@ -103,6 +109,18 @@ public void SourceAddWithDuplicateName()
Assert.True(result.StdOut.Contains("A source with the given name already exists and refers to a different location"));
}
+ ///
+ /// Test source add with duplicate source url.
+ ///
+ [Test]
+ public void SourceAddWithDuplicateSourceUrl()
+ {
+ // Add source with duplicate url should fail
+ var result = TestCommon.RunAICLICommand("source add", $"TestSource2 {Constants.TestSourceUrl}");
+ Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_ARG_ALREADY_EXISTS, result.ExitCode);
+ Assert.True(result.StdOut.Contains("A source with a different name already refers to this location"));
+ }
+
///
/// Test source add with invalid url.
///
diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h
index 3bdcdb2822..c01a1611f9 100644
--- a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h
+++ b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h
@@ -335,6 +335,9 @@ namespace AppInstaller::Repository
// Get a list of all available SourceDetails.
static std::vector GetCurrentSources();
+ // Get a default source type is the source type used when adding a source without specifying a type.
+ static std::string_view GetDefaultSourceType();
+
private:
void InitializeSourceReference(std::string_view name);
diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp
index 5c279f4de0..d178b6890d 100644
--- a/src/AppInstallerRepositoryCore/RepositorySource.cpp
+++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp
@@ -880,7 +880,7 @@ namespace AppInstaller::Repository
// AddSourceForDetails will also check for empty, but we need the actual type before that for validation.
if (sourceDetails.Type.empty())
{
- sourceDetails.Type = ISourceFactory::GetForType("")->TypeName();
+ sourceDetails.Type = GetDefaultSourceType();
}
AICLI_LOG(Repo, Info, << "Adding source: Name[" << sourceDetails.Name << "], Type[" << sourceDetails.Type << "], Arg[" << sourceDetails.Arg << "]");
@@ -1040,6 +1040,11 @@ namespace AppInstaller::Repository
}
}
+ std::string_view Source::GetDefaultSourceType()
+ {
+ return ISourceFactory::GetForType("")->TypeName();
+ }
+
#ifndef AICLI_DISABLE_TEST_HOOKS
void TestHook_SetSourceFactoryOverride(const std::string& type, std::function()>&& factory)
{