mirror of
https://github.com/withastro/astro.git
synced 2025-01-27 22:19:04 -05:00
db: dev --remote test fixture (#10527)
* feat: scaffold basic proxy * feat: setupDbServer util * feat: ASTRO_INTERNAL_TEST_REMOTE bypass * feat: basic --remote test * chore: add port incrementer
This commit is contained in:
parent
fda2a800e0
commit
95e3cc84e8
4 changed files with 208 additions and 1 deletions
|
@ -44,7 +44,7 @@ function astroDBIntegration(): AstroIntegration {
|
|||
|
||||
let dbPlugin: VitePlugin | undefined = undefined;
|
||||
const args = parseArgs(process.argv.slice(3));
|
||||
connectToStudio = args['remote'];
|
||||
connectToStudio = process.env.ASTRO_INTERNAL_TEST_REMOTE || args['remote'];
|
||||
|
||||
if (connectToStudio) {
|
||||
appToken = await getManagedAppTokenOrExit();
|
||||
|
|
|
@ -173,6 +173,9 @@ export async function getManagedAppTokenOrExit(token?: string): Promise<ManagedA
|
|||
if (token) {
|
||||
return new ManagedLocalAppToken(token);
|
||||
}
|
||||
if (process.env.ASTRO_INTERNAL_TEST_REMOTE) {
|
||||
return new ManagedLocalAppToken('fake' /* token ignored in test */);
|
||||
}
|
||||
const { ASTRO_STUDIO_APP_TOKEN } = getAstroStudioEnv();
|
||||
if (ASTRO_STUDIO_APP_TOKEN) {
|
||||
return new ManagedLocalAppToken(ASTRO_STUDIO_APP_TOKEN);
|
||||
|
|
|
@ -2,6 +2,7 @@ import { expect } from 'chai';
|
|||
import { load as cheerioLoad } from 'cheerio';
|
||||
import testAdapter from '../../astro/test/test-adapter.js';
|
||||
import { loadFixture } from '../../astro/test/test-utils.js';
|
||||
import { setupRemoteDbServer } from './test-utils.js';
|
||||
|
||||
describe('astro:db', () => {
|
||||
let fixture;
|
||||
|
@ -73,4 +74,68 @@ describe('astro:db', () => {
|
|||
expect($('.username').text()).to.equal('Mario');
|
||||
});
|
||||
});
|
||||
|
||||
describe('development --remote', () => {
|
||||
let devServer;
|
||||
let remoteDbServer;
|
||||
|
||||
before(async () => {
|
||||
remoteDbServer = await setupRemoteDbServer(fixture.config);
|
||||
devServer = await fixture.startDevServer();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer?.stop();
|
||||
await remoteDbServer?.stop();
|
||||
});
|
||||
|
||||
it('Prints the list of authors', async () => {
|
||||
const html = await fixture.fetch('/').then((res) => res.text());
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
const ul = $('.authors-list');
|
||||
expect(ul.children()).to.have.a.lengthOf(5);
|
||||
expect(ul.children().eq(0).text()).to.equal('Ben');
|
||||
});
|
||||
|
||||
it('Allows expression defaults for date columns', async () => {
|
||||
const html = await fixture.fetch('/').then((res) => res.text());
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
const themeAdded = $($('.themes-list .theme-added')[0]).text();
|
||||
expect(new Date(themeAdded).getTime()).to.not.be.NaN;
|
||||
});
|
||||
|
||||
it('Defaults can be overridden for dates', async () => {
|
||||
const html = await fixture.fetch('/').then((res) => res.text());
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
const themeAdded = $($('.themes-list .theme-added')[1]).text();
|
||||
expect(new Date(themeAdded).getTime()).to.not.be.NaN;
|
||||
});
|
||||
|
||||
it('Allows expression defaults for text columns', async () => {
|
||||
const html = await fixture.fetch('/').then((res) => res.text());
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
const themeOwner = $($('.themes-list .theme-owner')[0]).text();
|
||||
expect(themeOwner).to.equal('');
|
||||
});
|
||||
|
||||
it('Allows expression defaults for boolean columns', async () => {
|
||||
const html = await fixture.fetch('/').then((res) => res.text());
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
const themeDark = $($('.themes-list .theme-dark')[0]).text();
|
||||
expect(themeDark).to.equal('dark mode');
|
||||
});
|
||||
|
||||
it('text fields an be used as references', async () => {
|
||||
const html = await fixture.fetch('/login').then((res) => res.text());
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
expect($('.session-id').text()).to.equal('12345');
|
||||
expect($('.username').text()).to.equal('Mario');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
139
packages/db/test/test-utils.js
Normal file
139
packages/db/test/test-utils.js
Normal file
|
@ -0,0 +1,139 @@
|
|||
import { createClient } from '@libsql/client';
|
||||
import { createServer } from 'node:http';
|
||||
import { z } from 'zod';
|
||||
import { cli } from '../dist/core/cli/index.js';
|
||||
import { resolveDbConfig } from '../dist/core/load-file.js';
|
||||
import { getCreateTableQuery, getCreateIndexQueries } from '../dist/runtime/queries.js';
|
||||
|
||||
const singleQuerySchema = z.object({
|
||||
sql: z.string(),
|
||||
args: z.array(z.any()).or(z.record(z.string(), z.any())),
|
||||
});
|
||||
|
||||
const querySchema = singleQuerySchema.or(z.array(singleQuerySchema));
|
||||
|
||||
let portIncrementer = 8081;
|
||||
|
||||
/**
|
||||
* @param {import('astro').AstroConfig} astroConfig
|
||||
* @param {number | undefined} port
|
||||
*/
|
||||
export async function setupRemoteDbServer(astroConfig) {
|
||||
const port = portIncrementer++;
|
||||
process.env.ASTRO_STUDIO_REMOTE_DB_URL = `http://localhost:${port}`;
|
||||
process.env.ASTRO_INTERNAL_TEST_REMOTE = true;
|
||||
const server = createRemoteDbServer().listen(port);
|
||||
|
||||
const { dbConfig } = await resolveDbConfig(astroConfig);
|
||||
const setupQueries = [];
|
||||
for (const [name, table] of Object.entries(dbConfig?.tables ?? {})) {
|
||||
const createQuery = getCreateTableQuery(name, table);
|
||||
const indexQueries = getCreateIndexQueries(name, table);
|
||||
setupQueries.push(createQuery, ...indexQueries);
|
||||
}
|
||||
await fetch(`http://localhost:${port}/db/query`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(setupQueries.map((sql) => ({ sql, args: [] }))),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
await cli({
|
||||
config: astroConfig,
|
||||
flags: {
|
||||
_: [undefined, 'astro', 'db', 'execute', 'db/seed.ts'],
|
||||
remote: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
server,
|
||||
async stop() {
|
||||
delete process.env.ASTRO_STUDIO_REMOTE_DB_URL;
|
||||
delete process.env.ASTRO_INTERNAL_TEST_REMOTE;
|
||||
return new Promise((resolve, reject) => {
|
||||
server.close((err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createRemoteDbServer() {
|
||||
const dbClient = createClient({
|
||||
url: ':memory:',
|
||||
});
|
||||
const server = createServer((req, res) => {
|
||||
if (
|
||||
!req.url.startsWith('/db/query') ||
|
||||
req.method !== 'POST' ||
|
||||
req.headers['content-type'] !== 'application/json'
|
||||
) {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
const rawBody = [];
|
||||
req.on('data', (chunk) => {
|
||||
rawBody.push(chunk);
|
||||
});
|
||||
req.on('end', async () => {
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(Buffer.concat(rawBody).toString());
|
||||
} catch (e) {
|
||||
applyParseError(res);
|
||||
return;
|
||||
}
|
||||
const parsed = querySchema.safeParse(json);
|
||||
if (parsed.success === false) {
|
||||
applyParseError(res);
|
||||
return;
|
||||
}
|
||||
const body = parsed.data;
|
||||
try {
|
||||
const result = Array.isArray(body)
|
||||
? await dbClient.batch(body)
|
||||
: await dbClient.execute(body);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(result));
|
||||
} catch (e) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.statusMessage = e.message;
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: e.message,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
server.on('close', () => {
|
||||
dbClient.close();
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
function applyParseError(res) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.statusMessage = 'Invalid request body';
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
// Use JSON response with `success: boolean` property
|
||||
// to match remote error responses.
|
||||
success: false,
|
||||
})
|
||||
);
|
||||
}
|
Loading…
Add table
Reference in a new issue