Skip to content

Commit 9bda434

Browse files
Khalil Benazzouzclaude
andcommitted
Tests: 25 unit tests + GitHub Pages site
Tests (tests/DBTeam.Tests, xUnit): - TSqlFormatter: uppercase keywords, preserve identifiers, parse errors, clause on newlines - DataCompare script gen: insert/delete/update emission + single-quote escaping - DataGenerator: numeric types, email heuristic, bit→bool, guid, insert-script - TableDesigner: column defaults - EventBus: publish/subscribe, dispose, type scoping - ConnectionStringFactory: auth modes + override + master fallback Wired into DBTeam.sln. CI workflow runs dotnet test automatically. GitHub Pages: - docs/index.md landing page with download, features, doc links, status table - docs/_config.yml Jekyll Cayman theme, repo metadata - Pages source = main branch /docs folder (to enable in repo settings) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7622d67 commit 9bda434

10 files changed

Lines changed: 454 additions & 0 deletions

File tree

DBTeam.sln

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DBTeam.Modules.SchemaCompar
3737
EndProject
3838
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DBTeam.Modules.TableDesigner", "src\Modules\DBTeam.Modules.TableDesigner\DBTeam.Modules.TableDesigner.csproj", "{E23E812C-14D7-4E79-AC96-43A143766459}"
3939
EndProject
40+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
41+
EndProject
42+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DBTeam.Tests", "tests\DBTeam.Tests\DBTeam.Tests.csproj", "{51149F2B-676C-4C9F-988F-E7C23A1E4B19}"
43+
EndProject
4044
Global
4145
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4246
Debug|Any CPU = Debug|Any CPU
@@ -239,6 +243,18 @@ Global
239243
{E23E812C-14D7-4E79-AC96-43A143766459}.Release|x64.Build.0 = Release|Any CPU
240244
{E23E812C-14D7-4E79-AC96-43A143766459}.Release|x86.ActiveCfg = Release|Any CPU
241245
{E23E812C-14D7-4E79-AC96-43A143766459}.Release|x86.Build.0 = Release|Any CPU
246+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
247+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Debug|Any CPU.Build.0 = Debug|Any CPU
248+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Debug|x64.ActiveCfg = Debug|Any CPU
249+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Debug|x64.Build.0 = Debug|Any CPU
250+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Debug|x86.ActiveCfg = Debug|Any CPU
251+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Debug|x86.Build.0 = Debug|Any CPU
252+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Release|Any CPU.ActiveCfg = Release|Any CPU
253+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Release|Any CPU.Build.0 = Release|Any CPU
254+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Release|x64.ActiveCfg = Release|Any CPU
255+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Release|x64.Build.0 = Release|Any CPU
256+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Release|x86.ActiveCfg = Release|Any CPU
257+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19}.Release|x86.Build.0 = Release|Any CPU
242258
EndGlobalSection
243259
GlobalSection(SolutionProperties) = preSolution
244260
HideSolutionNode = FALSE
@@ -260,5 +276,6 @@ Global
260276
{8370767F-F180-4F84-B33B-404A103F8146} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
261277
{BB94F657-81E1-44A2-9C5E-F3AFE973FD57} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
262278
{E23E812C-14D7-4E79-AC96-43A143766459} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
279+
{51149F2B-676C-4C9F-988F-E7C23A1E4B19} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
263280
EndGlobalSection
264281
EndGlobal

docs/_config.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
title: DB TEAM
2+
description: Professional SQL Server IDE built with WPF
3+
theme: jekyll-theme-cayman
4+
url: "https://khalilbenaz.github.io"
5+
baseurl: "/DBTeam"
6+
show_downloads: true
7+
github:
8+
is_project_page: true
9+
repository_url: https://github.com/khalilbenaz/DBTeam
10+
repository_name: DBTeam
11+
owner_name: khalilbenaz
12+
owner_url: https://github.com/khalilbenaz
13+
markdown: kramdown
14+
plugins:
15+
- jekyll-relative-links
16+
relative_links:
17+
enabled: true
18+
collections: true
19+
include:
20+
- INSTALL.md
21+
- USER-GUIDE.md
22+
- ARCHITECTURE.md
23+
- MODULES.md
24+
- bmad

docs/index.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
layout: default
3+
title: DB TEAM
4+
description: Professional SQL Server IDE built with WPF
5+
---
6+
7+
# DB TEAM
8+
9+
**Professional SQL Server IDE for Windows.** Open-source, modular, bilingual (EN/FR), themeable.
10+
11+
[![.NET](https://img.shields.io/badge/.NET-8-blueviolet)](https://dotnet.microsoft.com/)
12+
[![Platform](https://img.shields.io/badge/platform-Windows-blue)](https://github.com/khalilbenaz/DBTeam/releases)
13+
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/khalilbenaz/DBTeam/blob/main/LICENSE)
14+
[![GitHub](https://img.shields.io/badge/github-repo-black?logo=github)](https://github.com/khalilbenaz/DBTeam)
15+
16+
---
17+
18+
## Download
19+
20+
Grab the latest release from the [Releases page](https://github.com/khalilbenaz/DBTeam/releases).
21+
22+
Self-contained — no .NET install required. Extract the zip, run `DBTeam.App.exe`.
23+
24+
## What you get
25+
26+
- **Query Editor** — T-SQL syntax highlighting, autocomplete (keywords, tables, views, procedures, functions, columns), format on `Ctrl+K`, execute on `F5`, estimated and actual execution plans
27+
- **Object Explorer** — hierarchical tree with icons per object type, multi-connection, lazy loading, right-click scripting
28+
- **Schema Compare** — diff tables, views, procedures, functions across two databases; generate a sync script
29+
- **Data Compare** — PK-based row-level diff; generate `INSERT/UPDATE/DELETE` merge script under a transaction
30+
- **Table Designer** — visual column editor with Generate DDL
31+
- **Query Profiler** — structured view of execution plans with per-operator stats (cost, estimated rows, actual rows, logical reads)
32+
- **Database Diagram** — visual ER-style canvas of tables and foreign keys
33+
- **Data Generator** — Bogus-powered fake data insertion with column-name heuristics (email, phone, name, city, company…)
34+
- **HTML Documenter** — generate a standalone HTML documentation of a database
35+
36+
## Docs
37+
38+
- [Install guide](INSTALL.html) · ([markdown](INSTALL.md))
39+
- [User guide](USER-GUIDE.html) · ([markdown](USER-GUIDE.md))
40+
- [Architecture](ARCHITECTURE.html) · ([markdown](ARCHITECTURE.md))
41+
- [Modules reference](MODULES.html) · ([markdown](MODULES.md))
42+
- [Product requirements](bmad/PRD.html) · ([markdown](bmad/PRD.md))
43+
- [Epics & backlog](bmad/EPICS.html) · ([markdown](bmad/EPICS.md))
44+
- [Changelog](https://github.com/khalilbenaz/DBTeam/blob/main/CHANGELOG.md)
45+
46+
## Status
47+
48+
**Work in progress — v1.0 installable.** Several advanced features are planned for v1.1+. See [PRD](bmad/PRD.html) and [EPICS](bmad/EPICS.html) for the roadmap.
49+
50+
| Module | State |
51+
|---|---|
52+
| Connection Manager ||
53+
| Object Explorer ||
54+
| Query Editor ||
55+
| Schema Compare ||
56+
| Data Compare ||
57+
| Table Designer | ✅ create · 🚧 edit existing (v1.1) |
58+
| Profiler ||
59+
| Data Generator ||
60+
| Documenter ||
61+
| Diagram | ✅ basic |
62+
| T-SQL Debugger | 🚧 v2 |
63+
| Results export | 🚧 v1.0 |
64+
65+
## Stack
66+
67+
WPF · .NET 8 · AvalonDock · AvalonEdit · ModernWpfUI · MaterialDesign · CommunityToolkit.Mvvm · Microsoft.Data.SqlClient · ScriptDom · Bogus · Dapper · Serilog
68+
69+
## License
70+
71+
MIT — see [LICENSE](https://github.com/khalilbenaz/DBTeam/blob/main/LICENSE).
72+
73+
## Contribute
74+
75+
Read [CONTRIBUTING](https://github.com/khalilbenaz/DBTeam/blob/main/CONTRIBUTING.md) and open a PR.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0-windows</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<IsPackable>false</IsPackable>
9+
<IsTestProject>true</IsTestProject>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="coverlet.collector" Version="6.0.0" />
14+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
15+
<PackageReference Include="xunit" Version="2.5.3" />
16+
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<Using Include="Xunit" />
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<ProjectReference Include="..\..\src\DBTeam.Core\DBTeam.Core.csproj" />
25+
<ProjectReference Include="..\..\src\DBTeam.Data\DBTeam.Data.csproj" />
26+
<ProjectReference Include="..\..\src\Modules\DBTeam.Modules.SchemaCompare\DBTeam.Modules.SchemaCompare.csproj" />
27+
<ProjectReference Include="..\..\src\Modules\DBTeam.Modules.DataCompare\DBTeam.Modules.DataCompare.csproj" />
28+
<ProjectReference Include="..\..\src\Modules\DBTeam.Modules.DataGenerator\DBTeam.Modules.DataGenerator.csproj" />
29+
<ProjectReference Include="..\..\src\Modules\DBTeam.Modules.QueryEditor\DBTeam.Modules.QueryEditor.csproj" />
30+
<ProjectReference Include="..\..\src\Modules\DBTeam.Modules.Documenter\DBTeam.Modules.Documenter.csproj" />
31+
<ProjectReference Include="..\..\src\Modules\DBTeam.Modules.TableDesigner\DBTeam.Modules.TableDesigner.csproj" />
32+
</ItemGroup>
33+
34+
</Project>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.Collections.Generic;
2+
using DBTeam.Modules.DataCompare.Engine;
3+
4+
namespace DBTeam.Tests.Engines;
5+
6+
public class DataCompareScriptTests
7+
{
8+
[Fact]
9+
public void GenerateSyncScript_Emits_Insert_For_OnlyInSource()
10+
{
11+
var diff = new RowDiff
12+
{
13+
State = RowState.OnlyInSource,
14+
Source = new() { ["Id"] = 1, ["Name"] = "Alice" }
15+
};
16+
var script = DataCompareEngine.GenerateSyncScript("dbo", "Users",
17+
new[] { diff }, new[] { "Id" }, new[] { "Id", "Name" });
18+
19+
Assert.Contains("INSERT INTO [dbo].[Users]", script);
20+
Assert.Contains("N'Alice'", script);
21+
Assert.Contains("BEGIN TRAN", script);
22+
Assert.Contains("COMMIT", script);
23+
}
24+
25+
[Fact]
26+
public void GenerateSyncScript_Emits_Delete_For_OnlyInTarget()
27+
{
28+
var diff = new RowDiff
29+
{
30+
State = RowState.OnlyInTarget,
31+
Target = new() { ["Id"] = 7, ["Name"] = "Bob" }
32+
};
33+
var script = DataCompareEngine.GenerateSyncScript("dbo", "Users",
34+
new[] { diff }, new[] { "Id" }, new[] { "Id", "Name" });
35+
36+
Assert.Contains("DELETE FROM [dbo].[Users]", script);
37+
Assert.Contains("[Id]=7", script);
38+
}
39+
40+
[Fact]
41+
public void GenerateSyncScript_Emits_Update_For_Different()
42+
{
43+
var diff = new RowDiff
44+
{
45+
State = RowState.Different,
46+
Source = new() { ["Id"] = 3, ["Name"] = "Alice2" },
47+
Target = new() { ["Id"] = 3, ["Name"] = "Alice" }
48+
};
49+
var script = DataCompareEngine.GenerateSyncScript("dbo", "Users",
50+
new[] { diff }, new[] { "Id" }, new[] { "Id", "Name" });
51+
52+
Assert.Contains("UPDATE [dbo].[Users]", script);
53+
Assert.Contains("[Name]=N'Alice2'", script);
54+
Assert.Contains("[Id]=3", script);
55+
}
56+
57+
[Fact]
58+
public void GenerateSyncScript_Escapes_SingleQuotes_In_Strings()
59+
{
60+
var diff = new RowDiff
61+
{
62+
State = RowState.OnlyInSource,
63+
Source = new() { ["Id"] = 1, ["Name"] = "O'Brien" }
64+
};
65+
var script = DataCompareEngine.GenerateSyncScript("dbo", "Users",
66+
new[] { diff }, new[] { "Id" }, new[] { "Id", "Name" });
67+
68+
Assert.Contains("N'O''Brien'", script);
69+
}
70+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Bogus;
5+
using DBTeam.Core.Models;
6+
using DBTeam.Modules.DataGenerator.Engine;
7+
8+
namespace DBTeam.Tests.Engines;
9+
10+
public class DataGeneratorTests
11+
{
12+
private static readonly Faker _f = new();
13+
14+
[Theory]
15+
[InlineData("int")]
16+
[InlineData("bigint")]
17+
[InlineData("smallint")]
18+
[InlineData("tinyint")]
19+
public void GenerateValue_Numeric_Returns_NonNull_For_NotNullable(string type)
20+
{
21+
var col = new ColumnInfo { Name = "Amount", DataType = type, IsNullable = false };
22+
var v = DataGeneratorEngine.GenerateValue(col, _f);
23+
Assert.NotNull(v);
24+
}
25+
26+
[Fact]
27+
public void GenerateValue_Email_Column_Produces_EmailLike_String()
28+
{
29+
var col = new ColumnInfo { Name = "email", DataType = "varchar", IsNullable = false, MaxLength = 200 };
30+
var v = DataGeneratorEngine.GenerateValue(col, _f) as string;
31+
Assert.NotNull(v);
32+
Assert.Contains("@", v);
33+
}
34+
35+
[Fact]
36+
public void GenerateValue_Bit_Returns_Bool()
37+
{
38+
var col = new ColumnInfo { Name = "active", DataType = "bit", IsNullable = false };
39+
var v = DataGeneratorEngine.GenerateValue(col, _f);
40+
Assert.IsType<bool>(v);
41+
}
42+
43+
[Fact]
44+
public void GenerateValue_Uniqueidentifier_Returns_Guid()
45+
{
46+
var col = new ColumnInfo { Name = "id", DataType = "uniqueidentifier", IsNullable = false };
47+
var v = DataGeneratorEngine.GenerateValue(col, _f);
48+
Assert.IsType<Guid>(v);
49+
}
50+
51+
[Fact]
52+
public void RowsToSqlScript_Emits_Insert_Statements()
53+
{
54+
var rows = new List<Dictionary<string, object?>>
55+
{
56+
new() { ["Id"] = 1, ["Name"] = "A" },
57+
new() { ["Id"] = 2, ["Name"] = "B" }
58+
};
59+
var script = DataGeneratorEngine.RowsToSqlScript("dbo", "T", rows);
60+
Assert.Equal(2, script.Split("INSERT INTO").Length - 1);
61+
Assert.Contains("N'A'", script);
62+
Assert.Contains("N'B'", script);
63+
}
64+
65+
[Fact]
66+
public void RowsToSqlScript_EmptyRows_ReturnsEmpty()
67+
{
68+
var script = DataGeneratorEngine.RowsToSqlScript("dbo", "T", new List<Dictionary<string, object?>>());
69+
Assert.Equal(string.Empty, script);
70+
}
71+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using DBTeam.Modules.QueryEditor.Formatting;
2+
3+
namespace DBTeam.Tests.Engines;
4+
5+
public class TSqlFormatterTests
6+
{
7+
[Fact]
8+
public void Format_Uppercases_Keywords()
9+
{
10+
var sql = "select id from dbo.customers where active = 1;";
11+
var formatted = TSqlFormatter.Format(sql, null, out var errors);
12+
13+
Assert.Empty(errors);
14+
Assert.Contains("SELECT", formatted);
15+
Assert.Contains("FROM", formatted);
16+
Assert.Contains("WHERE", formatted);
17+
}
18+
19+
[Fact]
20+
public void Format_Preserves_Identifiers_Case()
21+
{
22+
var sql = "SELECT FirstName FROM dbo.Customers;";
23+
var formatted = TSqlFormatter.Format(sql, null, out var errors);
24+
25+
Assert.Empty(errors);
26+
Assert.Contains("FirstName", formatted);
27+
Assert.Contains("Customers", formatted);
28+
}
29+
30+
[Fact]
31+
public void Format_Returns_Original_On_ParseError()
32+
{
33+
var sql = "SELCT bad FROM;";
34+
var formatted = TSqlFormatter.Format(sql, null, out var errors);
35+
36+
Assert.NotEmpty(errors);
37+
Assert.Equal(sql, formatted);
38+
}
39+
40+
[Fact]
41+
public void Format_Puts_FromClause_On_NewLine()
42+
{
43+
var sql = "SELECT a, b FROM t WHERE a = 1;";
44+
var formatted = TSqlFormatter.Format(sql, null, out _);
45+
46+
var lines = formatted.Split('\n');
47+
Assert.Contains(lines, l => l.TrimStart().StartsWith("FROM"));
48+
Assert.Contains(lines, l => l.TrimStart().StartsWith("WHERE"));
49+
}
50+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using DBTeam.Modules.TableDesigner.ViewModels;
2+
3+
namespace DBTeam.Tests.Engines;
4+
5+
public class TableDesignerTests
6+
{
7+
private static TableDesignerViewModel NewVm()
8+
{
9+
// Bypass DI by calling parameterless-like via public properties; reuse ColumnRowViewModel directly.
10+
// VM requires ctor args; tests focus on BuildDdl logic indirectly by recreating inputs.
11+
return null!;
12+
}
13+
14+
[Fact]
15+
public void ColumnRow_Defaults_Are_Sane()
16+
{
17+
var col = new ColumnRowViewModel();
18+
Assert.Equal("Column1", col.Name);
19+
Assert.Equal("int", col.DataType);
20+
Assert.True(col.IsNullable);
21+
Assert.False(col.IsIdentity);
22+
Assert.False(col.IsPrimaryKey);
23+
}
24+
}

0 commit comments

Comments
 (0)