Skip to content

Commit db53005

Browse files
Implement Realtime Scanners (ASCA, Secrets, IaC, Containers, OSS) with File-Level Debouncing and VS Integration (AST-109633) (#311)
1 parent 945ed82 commit db53005

66 files changed

Lines changed: 8178 additions & 848 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ast-visual-studio-extension-tests/ast-visual-studio-extension-tests.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4+
<!-- net6.0-windows7.0 matches NuGet assets target key; avoids NETSDK1005 with some SDK 10 + restore combinations. -->
45
<TargetFramework>net60-windows</TargetFramework>
5-
<IsTestProject>true</IsTestProject>
6+
<IsTestProject>true</IsTestProject>
7+
<!-- Transitive VS SDK packages target net472; restore fallback is expected for extension test host. -->
8+
<NoWarn>$(NoWarn);NU1701</NoWarn>
69
</PropertyGroup>
710

811
<ItemGroup>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
using ast_visual_studio_extension.CxCLI;
2+
using ast_visual_studio_extension.CxExtension.CxAssist.Realtime.Asca;
3+
using ast_visual_studio_extension.CxWrapper.Models;
4+
using Moq;
5+
using System;
6+
using Xunit;
7+
8+
namespace ast_visual_studio_extension_tests.cx_integration_tests.Services
9+
{
10+
/// <summary>
11+
/// Integration tests for ASCA realtime scanner service.
12+
/// Requires VS UI context (ThreadHelper, JoinableTaskFactory).
13+
/// Run separately from unit tests; skip in CI if VS test host unavailable.
14+
/// </summary>
15+
[Trait("Category", "Integration")]
16+
public class AscaServiceTests
17+
{
18+
private readonly CxWrapper _wrapperInstance;
19+
20+
public AscaServiceTests()
21+
{
22+
// Create a real CxConfig for testing (doesn't make actual HTTP calls in unit tests)
23+
var config = new CxConfig
24+
{
25+
ApiKey = "test-api-key"
26+
};
27+
28+
// Create a real CxWrapper instance - it won't make actual calls unless explicitly invoked
29+
_wrapperInstance = new CxWrapper(config, typeof(AscaServiceTests));
30+
}
31+
32+
[Fact]
33+
public void AscaService_GetInstance_ReturnsServiceInstance()
34+
{
35+
// Get first instance
36+
var service1 = AscaService.GetInstance(_wrapperInstance);
37+
38+
Assert.NotNull(service1);
39+
}
40+
41+
[Fact]
42+
public void AscaService_GetInstance_ReturnsSingletonInstance()
43+
{
44+
var service1 = AscaService.GetInstance(_wrapperInstance);
45+
var service2 = AscaService.GetInstance(_wrapperInstance);
46+
47+
Assert.Same(service1, service2);
48+
}
49+
50+
[Theory]
51+
[InlineData("test.cs")]
52+
[InlineData("test.java")]
53+
[InlineData("test.go")]
54+
[InlineData("test.py")]
55+
[InlineData("test.js")]
56+
[InlineData("test.jsx")]
57+
public void AscaService_ShouldScanFile_WithValidExtension_ReturnsTrue(string filePath)
58+
{
59+
var service = AscaService.GetInstance(_wrapperInstance);
60+
61+
Assert.True(service.ShouldScanFile(filePath));
62+
}
63+
64+
[Theory]
65+
[InlineData("test.txt")]
66+
[InlineData("test.cpp")]
67+
[InlineData("test.xml")]
68+
[InlineData("test.yml")]
69+
public void AscaService_ShouldScanFile_WithInvalidExtension_ReturnsFalse(string filePath)
70+
{
71+
var service = AscaService.GetInstance(_wrapperInstance);
72+
73+
Assert.False(service.ShouldScanFile(filePath));
74+
}
75+
76+
[Theory]
77+
[InlineData("C:\\node_modules\\app.js")]
78+
[InlineData("C:\\venv\\script.py")]
79+
[InlineData("C:\\dist\\bundle.js")]
80+
public void AscaService_ShouldScanFile_InExcludedPath_ReturnsFalse(string filePath)
81+
{
82+
var service = AscaService.GetInstance(_wrapperInstance);
83+
84+
Assert.False(service.ShouldScanFile(filePath));
85+
}
86+
87+
[Fact]
88+
public void AscaService_ShouldScanFile_WithNull_ReturnsFalse()
89+
{
90+
var service = AscaService.GetInstance(_wrapperInstance);
91+
92+
Assert.False(service.ShouldScanFile(null));
93+
}
94+
95+
[Fact]
96+
public void AscaService_ShouldScanFile_WithEmpty_ReturnsFalse()
97+
{
98+
var service = AscaService.GetInstance(_wrapperInstance);
99+
100+
Assert.False(service.ShouldScanFile(string.Empty));
101+
}
102+
103+
[Fact]
104+
public async System.Threading.Tasks.Task AscaService_UnregisterAsync_AllowsReinitializationAsync()
105+
{
106+
var service1 = AscaService.GetInstance(_wrapperInstance);
107+
await service1.UnregisterAsync();
108+
109+
// After unregister, next GetInstance should create new instance
110+
var service2 = AscaService.GetInstance(_wrapperInstance);
111+
112+
// Should be different instances after unregister
113+
Assert.NotSame(service1, service2);
114+
}
115+
116+
[Fact]
117+
public void AscaService_MultipleGetInstance_WithDifferentWrappers_StillReturnsSingleton()
118+
{
119+
var config1 = new CxConfig { ApiKey = "test1" };
120+
var config2 = new CxConfig { ApiKey = "test2" };
121+
var wrapper1 = new Mock<ast_visual_studio_extension.CxCLI.CxWrapper>(config1, typeof(AscaServiceTests));
122+
var wrapper2 = new Mock<ast_visual_studio_extension.CxCLI.CxWrapper>(config2, typeof(AscaServiceTests));
123+
124+
var service1 = AscaService.GetInstance(wrapper1.Object);
125+
var service2 = AscaService.GetInstance(wrapper2.Object);
126+
127+
// Singleton pattern returns same instance regardless of wrapper
128+
Assert.Same(service1, service2);
129+
}
130+
}
131+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using ast_visual_studio_extension.CxCLI;
2+
using ast_visual_studio_extension.CxExtension.CxAssist.Realtime.Containers;
3+
using ast_visual_studio_extension.CxWrapper.Models;
4+
using System;
5+
using Xunit;
6+
7+
namespace ast_visual_studio_extension_tests.cx_integration_tests.Services
8+
{
9+
/// <summary>
10+
/// Integration tests for Containers realtime scanner service.
11+
/// Requires VS UI context (ThreadHelper, JoinableTaskFactory).
12+
/// Run separately from unit tests; skip in CI if VS test host unavailable.
13+
/// </summary>
14+
[Trait("Category", "Integration")]
15+
public class ContainersServiceTests
16+
{
17+
private readonly CxWrapper _wrapperInstance;
18+
19+
public ContainersServiceTests()
20+
{
21+
var config = new CxConfig
22+
{
23+
ApiKey = "test-api-key"
24+
};
25+
26+
_wrapperInstance = new CxWrapper(config, typeof(ContainersServiceTests));
27+
}
28+
29+
[Fact]
30+
public void ContainersService_GetInstance_ReturnsServiceInstance()
31+
{
32+
var service = ContainersService.GetInstance(_wrapperInstance, "docker");
33+
34+
Assert.NotNull(service);
35+
}
36+
37+
[Fact]
38+
public void ContainersService_GetInstance_ReturnsSingletonInstance()
39+
{
40+
var service1 = ContainersService.GetInstance(_wrapperInstance, "docker");
41+
var service2 = ContainersService.GetInstance(_wrapperInstance, "podman");
42+
43+
// Singleton pattern returns same instance regardless of container engine
44+
Assert.Same(service1, service2);
45+
}
46+
47+
[Theory]
48+
[InlineData("dockerfile")]
49+
[InlineData("docker-compose.yml")]
50+
[InlineData("C:\\helm\\values.yaml")]
51+
public void ContainersService_ShouldScanFile_WithValidType_ReturnsTrue(string filePath)
52+
{
53+
var service = ContainersService.GetInstance(_wrapperInstance, "docker");
54+
55+
Assert.True(service.ShouldScanFile(filePath));
56+
}
57+
58+
[Theory]
59+
[InlineData("test.py")]
60+
[InlineData("main.tf")]
61+
[InlineData("package.json")]
62+
public void ContainersService_ShouldScanFile_WithInvalidType_ReturnsFalse(string filePath)
63+
{
64+
var service = ContainersService.GetInstance(_wrapperInstance, "docker");
65+
66+
Assert.False(service.ShouldScanFile(filePath));
67+
}
68+
69+
[Fact]
70+
public void ContainersService_ShouldScanFile_WithNull_ReturnsFalse()
71+
{
72+
var service = ContainersService.GetInstance(_wrapperInstance, "docker");
73+
74+
Assert.False(service.ShouldScanFile(null));
75+
}
76+
77+
[Fact]
78+
public async System.Threading.Tasks.Task ContainersService_UnregisterAsync_AllowsReinitializationAsync()
79+
{
80+
var service1 = ContainersService.GetInstance(_wrapperInstance, "docker");
81+
await service1.UnregisterAsync();
82+
83+
var service2 = ContainersService.GetInstance(_wrapperInstance, "docker");
84+
85+
Assert.NotSame(service1, service2);
86+
}
87+
88+
[Theory]
89+
[InlineData("docker")]
90+
[InlineData("podman")]
91+
public void ContainersService_GetInstance_AcceptsContainerEngine(string engine)
92+
{
93+
var service = ContainersService.GetInstance(_wrapperInstance, engine);
94+
95+
Assert.NotNull(service);
96+
}
97+
}
98+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using ast_visual_studio_extension.CxCLI;
2+
using ast_visual_studio_extension.CxExtension.CxAssist.Realtime.Iac;
3+
using ast_visual_studio_extension.CxWrapper.Models;
4+
using System;
5+
using Xunit;
6+
7+
namespace ast_visual_studio_extension_tests.cx_integration_tests.Services
8+
{
9+
/// <summary>
10+
/// Integration tests for IaC realtime scanner service.
11+
/// Requires VS UI context (ThreadHelper, JoinableTaskFactory).
12+
/// Run separately from unit tests; skip in CI if VS test host unavailable.
13+
/// </summary>
14+
[Trait("Category", "Integration")]
15+
public class IacServiceTests
16+
{
17+
private readonly CxWrapper _wrapperInstance;
18+
19+
public IacServiceTests()
20+
{
21+
var config = new CxConfig
22+
{
23+
ApiKey = "test-api-key"
24+
};
25+
26+
_wrapperInstance = new CxWrapper(config, typeof(IacServiceTests));
27+
}
28+
29+
[Fact]
30+
public void IacService_GetInstance_ReturnsServiceInstance()
31+
{
32+
var service = IacService.GetInstance(_wrapperInstance);
33+
34+
Assert.NotNull(service);
35+
}
36+
37+
[Fact]
38+
public void IacService_GetInstance_ReturnsSingletonInstance()
39+
{
40+
var service1 = IacService.GetInstance(_wrapperInstance);
41+
var service2 = IacService.GetInstance(_wrapperInstance);
42+
43+
Assert.Same(service1, service2);
44+
}
45+
46+
[Theory]
47+
[InlineData("main.tf")]
48+
[InlineData("config.yaml")]
49+
[InlineData("data.json")]
50+
[InlineData("dockerfile")]
51+
public void IacService_ShouldScanFile_WithValidType_ReturnsTrue(string filePath)
52+
{
53+
var service = IacService.GetInstance(_wrapperInstance);
54+
55+
Assert.True(service.ShouldScanFile(filePath));
56+
}
57+
58+
[Theory]
59+
[InlineData("test.py")]
60+
[InlineData("app.cs")]
61+
[InlineData("script.sh")]
62+
public void IacService_ShouldScanFile_WithInvalidType_ReturnsFalse(string filePath)
63+
{
64+
var service = IacService.GetInstance(_wrapperInstance);
65+
66+
Assert.False(service.ShouldScanFile(filePath));
67+
}
68+
69+
[Fact]
70+
public void IacService_ShouldScanFile_WithNull_ReturnsFalse()
71+
{
72+
var service = IacService.GetInstance(_wrapperInstance);
73+
74+
Assert.False(service.ShouldScanFile(null));
75+
}
76+
77+
[Fact]
78+
public async System.Threading.Tasks.Task IacService_UnregisterAsync_AllowsReinitializationAsync()
79+
{
80+
var service1 = IacService.GetInstance(_wrapperInstance);
81+
await service1.UnregisterAsync();
82+
83+
var service2 = IacService.GetInstance(_wrapperInstance);
84+
85+
Assert.NotSame(service1, service2);
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)