Use the desktop's own monospace font in the terminal
Read org.gnome.desktop.interface monospace-font-name at startup rather than hardcoding a family, so the terminal matches GNOME Terminal exactly and keeps matching after the system font changes. Pango reports points and CSS wants pixels, so 13 becomes 17.33px — rendering it as 13px made the terminal a third too small, which reads as the wrong font rather than the wrong size. The size is the trailing number, since families may contain digits. Families are always quoted, because an unquoted "Noto Sans Mono" is a three-family stack to CSS. Also fixes tests/dial.test.ts, which hardcoded the test server's fingerprint and so went red after every /tmp cleanup. It now asks ssh-keygen, which is a stronger assertion anyway: the expected value comes from the reference implementation rather than from this project's own fingerprint code.
This commit is contained in:
+57
-10
@@ -1,3 +1,4 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { userInfo } from 'node:os'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
@@ -28,9 +29,55 @@ import { createTempDb, type TempDb } from './helpers'
|
||||
*/
|
||||
const TEST_PORT = 2222
|
||||
const TEST_USER_KEY = '/tmp/testsshd/user_key'
|
||||
const EXPECTED_FINGERPRINT = 'SHA256:ASmbFHszdZLp5YkSiJnqmGfAhpZKOZwQJnk3rLUo4Xo'
|
||||
const TEST_HOST_KEY = '/tmp/testsshd/host_key.pub'
|
||||
|
||||
const prerequisitesPresent = existsSync(TEST_USER_KEY)
|
||||
// ssh-keygen is what tells us the expected value, so it is part of the
|
||||
// prerequisite too.
|
||||
const prerequisitesPresent =
|
||||
existsSync(TEST_USER_KEY) &&
|
||||
existsSync(TEST_HOST_KEY) &&
|
||||
(() => {
|
||||
try {
|
||||
execFileSync('ssh-keygen', ['-lf', TEST_HOST_KEY], { stdio: 'ignore' })
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})()
|
||||
|
||||
/**
|
||||
* Ask `ssh-keygen` what the server's fingerprint is, rather than writing it
|
||||
* down here.
|
||||
*
|
||||
* A literal would be tied to whichever key happened to exist when it was
|
||||
* pasted in, and the test key is regenerated whenever `/tmp` is cleared — so
|
||||
* the suite would go red after every reboot for a reason that has nothing to
|
||||
* do with the code. A check that cries wolf is a check people learn to ignore.
|
||||
*
|
||||
* It is also a stronger assertion: the expected value comes from the reference
|
||||
* implementation, not from this project's own fingerprint code.
|
||||
*/
|
||||
function expectedFingerprint(): string | null {
|
||||
try {
|
||||
const output = execFileSync('ssh-keygen', ['-lf', TEST_HOST_KEY], { encoding: 'utf8' })
|
||||
return /SHA256:[A-Za-z0-9+/]+=*/.exec(output)?.[0] ?? null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read fresh at each call rather than captured once.
|
||||
*
|
||||
* A module-level constant would be fine here, but a function makes it obvious
|
||||
* at every call site that this value comes from outside — which is the whole
|
||||
* point of not hardcoding it.
|
||||
*/
|
||||
function EXPECTED_FINGERPRINT(): string {
|
||||
const value = expectedFingerprint()
|
||||
if (!value) throw new Error('could not read the test sshd host key fingerprint')
|
||||
return value
|
||||
}
|
||||
|
||||
let temp: TempDb
|
||||
const openClients: Client[] = []
|
||||
@@ -89,7 +136,7 @@ describe.runIf(prerequisitesPresent)('dial — trust on first use', () => {
|
||||
if (result.failure.reason !== 'unknown-host-key') return
|
||||
|
||||
// The fingerprint shown must be the real one, or the user cannot check it.
|
||||
expect(result.failure.fingerprint).toBe(EXPECTED_FINGERPRINT)
|
||||
expect(result.failure.fingerprint).toBe(EXPECTED_FINGERPRINT())
|
||||
expect(result.failure.keyType).toBe('ssh-ed25519')
|
||||
})
|
||||
|
||||
@@ -102,21 +149,21 @@ describe.runIf(prerequisitesPresent)('dial — trust on first use', () => {
|
||||
})
|
||||
|
||||
it('connects when the presented fingerprint is trusted', async () => {
|
||||
const result = await attempt({}, EXPECTED_FINGERPRINT)
|
||||
const result = await attempt({}, EXPECTED_FINGERPRINT())
|
||||
|
||||
expect(result.ok).toBe(true)
|
||||
if (result.ok) expect(result.fingerprint).toBe(EXPECTED_FINGERPRINT)
|
||||
if (result.ok) expect(result.fingerprint).toBe(EXPECTED_FINGERPRINT())
|
||||
})
|
||||
|
||||
it('remembers the key once trusted', async () => {
|
||||
await attempt({}, EXPECTED_FINGERPRINT)
|
||||
await attempt({}, EXPECTED_FINGERPRINT())
|
||||
|
||||
const known = findKnownHost(temp.db, '127.0.0.1', TEST_PORT, 'ssh-ed25519')
|
||||
expect(known?.fingerprint).toBe(EXPECTED_FINGERPRINT)
|
||||
expect(known?.fingerprint).toBe(EXPECTED_FINGERPRINT())
|
||||
})
|
||||
|
||||
it('connects without a prompt once the key is recorded', async () => {
|
||||
await attempt({}, EXPECTED_FINGERPRINT)
|
||||
await attempt({}, EXPECTED_FINGERPRINT())
|
||||
|
||||
const second = await attempt()
|
||||
|
||||
@@ -143,7 +190,7 @@ describe.runIf(prerequisitesPresent)('dial — trust on first use', () => {
|
||||
expect(result.failure.reason).toBe('host-key-mismatch')
|
||||
if (result.failure.reason !== 'host-key-mismatch') return
|
||||
expect(result.failure.expected).toBe('SHA256:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB')
|
||||
expect(result.failure.actual).toBe(EXPECTED_FINGERPRINT)
|
||||
expect(result.failure.actual).toBe(EXPECTED_FINGERPRINT())
|
||||
})
|
||||
|
||||
it('reports a readable failure for an unreadable key file', async () => {
|
||||
@@ -174,7 +221,7 @@ describe.runIf(prerequisitesPresent)('dial — trust on first use', () => {
|
||||
// never reaches authentication.
|
||||
const result = await attempt(
|
||||
{ authType: 'password' },
|
||||
EXPECTED_FINGERPRINT,
|
||||
EXPECTED_FINGERPRINT(),
|
||||
'definitely-not-the-password',
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user