kin: auto-commit after pipeline
This commit is contained in:
parent
6c2da26b6c
commit
396f5193d3
3 changed files with 66 additions and 1 deletions
|
|
@ -2151,3 +2151,26 @@ def test_create_project_with_test_command(client):
|
||||||
conn.close()
|
conn.close()
|
||||||
assert row is not None
|
assert row is not None
|
||||||
assert row[0] == "npm test"
|
assert row[0] == "npm test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_patch_project_test_command_empty_string_stores_empty(client):
|
||||||
|
"""KIN-ARCH-008: PATCH с пустой строкой сохраняет пустую строку (не NULL, в отличие от deploy_command)."""
|
||||||
|
client.patch("/api/projects/p1", json={"test_command": "pytest -v"})
|
||||||
|
client.patch("/api/projects/p1", json={"test_command": ""})
|
||||||
|
|
||||||
|
from core.db import init_db
|
||||||
|
conn = init_db(api_module.DB_PATH)
|
||||||
|
row = conn.execute("SELECT test_command FROM projects WHERE id = 'p1'").fetchone()
|
||||||
|
conn.close()
|
||||||
|
assert row[0] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_projects_includes_test_command(client):
|
||||||
|
"""KIN-ARCH-008: GET /api/projects возвращает test_command — нужно для инициализации фронтенда."""
|
||||||
|
client.patch("/api/projects/p1", json={"test_command": "cargo test"})
|
||||||
|
r = client.get("/api/projects")
|
||||||
|
assert r.status_code == 200
|
||||||
|
projects = r.json()
|
||||||
|
p1 = next((p for p in projects if p["id"] == "p1"), None)
|
||||||
|
assert p1 is not None
|
||||||
|
assert p1["test_command"] == "cargo test"
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ export interface Project {
|
||||||
autocommit_enabled: number | null
|
autocommit_enabled: number | null
|
||||||
obsidian_vault_path: string | null
|
obsidian_vault_path: string | null
|
||||||
deploy_command: string | null
|
deploy_command: string | null
|
||||||
|
test_command: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
total_tasks: number
|
total_tasks: number
|
||||||
done_tasks: number
|
done_tasks: number
|
||||||
|
|
@ -315,7 +316,7 @@ export const api = {
|
||||||
post<{ updated: string[]; count: number }>(`/projects/${projectId}/audit/apply`, { task_ids: taskIds }),
|
post<{ updated: string[]; count: number }>(`/projects/${projectId}/audit/apply`, { task_ids: taskIds }),
|
||||||
patchTask: (id: string, data: { status?: string; execution_mode?: string; priority?: number; route_type?: string; title?: string; brief_text?: string; acceptance_criteria?: string }) =>
|
patchTask: (id: string, data: { status?: string; execution_mode?: string; priority?: number; route_type?: string; title?: string; brief_text?: string; acceptance_criteria?: string }) =>
|
||||||
patch<Task>(`/tasks/${id}`, data),
|
patch<Task>(`/tasks/${id}`, data),
|
||||||
patchProject: (id: string, data: { execution_mode?: string; autocommit_enabled?: boolean; obsidian_vault_path?: string; deploy_command?: string; project_type?: string; ssh_host?: string; ssh_user?: string; ssh_key_path?: string; ssh_proxy_jump?: string }) =>
|
patchProject: (id: string, data: { execution_mode?: string; autocommit_enabled?: boolean; obsidian_vault_path?: string; deploy_command?: string; test_command?: string; project_type?: string; ssh_host?: string; ssh_user?: string; ssh_key_path?: string; ssh_proxy_jump?: string }) =>
|
||||||
patch<Project>(`/projects/${id}`, data),
|
patch<Project>(`/projects/${id}`, data),
|
||||||
deployProject: (projectId: string) =>
|
deployProject: (projectId: string) =>
|
||||||
post<DeployResult>(`/projects/${projectId}/deploy`, {}),
|
post<DeployResult>(`/projects/${projectId}/deploy`, {}),
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,14 @@ import { api, type Project, type ObsidianSyncResult } from '../api'
|
||||||
const projects = ref<Project[]>([])
|
const projects = ref<Project[]>([])
|
||||||
const vaultPaths = ref<Record<string, string>>({})
|
const vaultPaths = ref<Record<string, string>>({})
|
||||||
const deployCommands = ref<Record<string, string>>({})
|
const deployCommands = ref<Record<string, string>>({})
|
||||||
|
const testCommands = ref<Record<string, string>>({})
|
||||||
const saving = ref<Record<string, boolean>>({})
|
const saving = ref<Record<string, boolean>>({})
|
||||||
const savingDeploy = ref<Record<string, boolean>>({})
|
const savingDeploy = ref<Record<string, boolean>>({})
|
||||||
|
const savingTest = ref<Record<string, boolean>>({})
|
||||||
const syncing = ref<Record<string, boolean>>({})
|
const syncing = ref<Record<string, boolean>>({})
|
||||||
const saveStatus = ref<Record<string, string>>({})
|
const saveStatus = ref<Record<string, string>>({})
|
||||||
const saveDeployStatus = ref<Record<string, string>>({})
|
const saveDeployStatus = ref<Record<string, string>>({})
|
||||||
|
const saveTestStatus = ref<Record<string, string>>({})
|
||||||
const syncResults = ref<Record<string, ObsidianSyncResult | null>>({})
|
const syncResults = ref<Record<string, ObsidianSyncResult | null>>({})
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
|
|
@ -19,6 +22,7 @@ onMounted(async () => {
|
||||||
for (const p of projects.value) {
|
for (const p of projects.value) {
|
||||||
vaultPaths.value[p.id] = p.obsidian_vault_path ?? ''
|
vaultPaths.value[p.id] = p.obsidian_vault_path ?? ''
|
||||||
deployCommands.value[p.id] = p.deploy_command ?? ''
|
deployCommands.value[p.id] = p.deploy_command ?? ''
|
||||||
|
testCommands.value[p.id] = p.test_command ?? ''
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = String(e)
|
error.value = String(e)
|
||||||
|
|
@ -51,6 +55,19 @@ async function saveDeployCommand(projectId: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveTestCommand(projectId: string) {
|
||||||
|
savingTest.value[projectId] = true
|
||||||
|
saveTestStatus.value[projectId] = ''
|
||||||
|
try {
|
||||||
|
await api.patchProject(projectId, { test_command: testCommands.value[projectId] })
|
||||||
|
saveTestStatus.value[projectId] = 'Saved'
|
||||||
|
} catch (e) {
|
||||||
|
saveTestStatus.value[projectId] = `Error: ${e}`
|
||||||
|
} finally {
|
||||||
|
savingTest.value[projectId] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function runSync(projectId: string) {
|
async function runSync(projectId: string) {
|
||||||
syncing.value[projectId] = true
|
syncing.value[projectId] = true
|
||||||
syncResults.value[projectId] = null
|
syncResults.value[projectId] = null
|
||||||
|
|
@ -112,6 +129,30 @@ async function runSync(projectId: string) {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="block text-xs text-gray-400 mb-1">Test Command</label>
|
||||||
|
<input
|
||||||
|
v-model="testCommands[project.id]"
|
||||||
|
type="text"
|
||||||
|
placeholder="make test"
|
||||||
|
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-gray-200 font-mono focus:outline-none focus:border-gray-500"
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-gray-600 mt-1">Команда запуска тестов, выполняется через shell в директории проекта.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-3 flex-wrap mb-3">
|
||||||
|
<button
|
||||||
|
@click="saveTestCommand(project.id)"
|
||||||
|
:disabled="savingTest[project.id]"
|
||||||
|
class="px-3 py-1.5 text-sm bg-gray-700 hover:bg-gray-600 text-gray-200 rounded disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{{ savingTest[project.id] ? 'Saving…' : 'Save Test' }}
|
||||||
|
</button>
|
||||||
|
<span v-if="saveTestStatus[project.id]" class="text-xs" :class="saveTestStatus[project.id].startsWith('Error') ? 'text-red-400' : 'text-green-400'">
|
||||||
|
{{ saveTestStatus[project.id] }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-3 flex-wrap">
|
<div class="flex items-center gap-3 flex-wrap">
|
||||||
<button
|
<button
|
||||||
@click="saveVaultPath(project.id)"
|
@click="saveVaultPath(project.id)"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue