diff --git a/packages/js-sdk/tests/template/fileContext.test.ts b/packages/js-sdk/tests/template/fileContext.test.ts new file mode 100644 index 0000000000..620cb30102 --- /dev/null +++ b/packages/js-sdk/tests/template/fileContext.test.ts @@ -0,0 +1,41 @@ +import os from 'node:os' +import { describe, expect, test } from 'vitest' +import { Template } from '../../src' + +// Path to a committed, read-only fixture, relative to this file's directory. +// The implicit file context is the directory of the file that calls Template(), +// so it is always inside the repository — which is why the fixture is committed +// and never written to, rather than generated into a temp directory. +const fixturePath = 'fixtures/hello.txt' + +describe('file context', () => { + test('defaults to the directory of the caller of Template()', async () => { + const implicit = Template().fromBaseImage().copy(fixturePath, 'hello.txt') + const explicit = Template({ fileContextPath: __dirname }) + .fromBaseImage() + .copy(fixturePath, 'hello.txt') + + // toJSON hashes each COPY's files, so the two serializations only match if + // the implicit context resolved to this file's directory, the glob found + // the fixture there, and its contents were read. This is the only test that + // exercises the implicit default end to end — every other template test + // passes fileContextPath explicitly, and the unit test for + // getCallerDirectory covers the helper in isolation, not its use here. + expect(await Template.toJSON(implicit)).toBe( + await Template.toJSON(explicit) + ) + }) + + test('fails to resolve a source that is not in the context', async () => { + // Keeps the assertion above from passing vacuously: the hashes match + // because the fixture was found, not because a missing file hashes the + // same either way. + const wrongContext = Template({ fileContextPath: os.tmpdir() }) + .fromBaseImage() + .copy(fixturePath, 'hello.txt') + + await expect(Template.toJSON(wrongContext)).rejects.toThrow( + /No files found/ + ) + }) +}) diff --git a/packages/js-sdk/tests/template/fixtures/hello.txt b/packages/js-sdk/tests/template/fixtures/hello.txt new file mode 100644 index 0000000000..2caac9087c --- /dev/null +++ b/packages/js-sdk/tests/template/fixtures/hello.txt @@ -0,0 +1,3 @@ +This file is the fixture for the implicit file-context test. It is read-only: +the implicit context is the directory of the file that calls Template(), which +is inside the repository, so nothing may be written here at test time. diff --git a/packages/js-sdk/tests/template/utils/getAllFilesInPath.test.ts b/packages/js-sdk/tests/template/utils/getAllFilesInPath.test.ts index 30d888fdef..836e1da837 100644 --- a/packages/js-sdk/tests/template/utils/getAllFilesInPath.test.ts +++ b/packages/js-sdk/tests/template/utils/getAllFilesInPath.test.ts @@ -1,24 +1,20 @@ -import { expect, test, describe, beforeAll, afterAll, beforeEach } from 'vitest' +import { expect, test, describe, afterEach, beforeEach } from 'vitest' import { writeFile, mkdir, mkdtemp, rm } from 'fs/promises' import { tmpdir } from 'os' import { join, basename } from 'path' import { getAllFilesInPath } from '../../../src/template/utils' describe('getAllFilesInPath', () => { - // A temp directory, so a test run never writes into the repository tree. + // A fresh temp directory per test, so a run never writes into the repository + // tree and no fixture leaks from one test into the next. let testDir: string - beforeAll(async () => { + beforeEach(async () => { testDir = await mkdtemp(join(tmpdir(), 'getAllFilesInPath-test-')) }) - afterAll(async () => { - await rm(testDir, { recursive: true, force: true }) - }) - - beforeEach(async () => { + afterEach(async () => { await rm(testDir, { recursive: true, force: true }) - await mkdir(testDir, { recursive: true }) }) test('should return files matching a simple pattern', async () => { diff --git a/packages/js-sdk/tests/template/utils/spoolTarArchive.test.ts b/packages/js-sdk/tests/template/utils/spoolTarArchive.test.ts index 9ad88e7e2f..76da5cbac7 100644 --- a/packages/js-sdk/tests/template/utils/spoolTarArchive.test.ts +++ b/packages/js-sdk/tests/template/utils/spoolTarArchive.test.ts @@ -1,4 +1,4 @@ -import { expect, test, describe, beforeAll, afterAll, beforeEach } from 'vitest' +import { expect, test, describe, afterEach, beforeEach } from 'vitest' import { writeFile, mkdir, @@ -15,20 +15,16 @@ import * as tar from 'tar' import { ReadEntry } from 'tar' describe('spoolTarArchive', () => { - // A temp directory, so a test run never writes into the repository tree. + // A fresh temp directory per test, so a run never writes into the repository + // tree and no fixture leaks from one test into the next. let testDir: string - beforeAll(async () => { + beforeEach(async () => { testDir = await mkdtemp(join(tmpdir(), 'spoolTarArchive-test-')) }) - afterAll(async () => { - await rm(testDir, { recursive: true, force: true }) - }) - - beforeEach(async () => { + afterEach(async () => { await rm(testDir, { recursive: true, force: true }) - await mkdir(testDir, { recursive: true }) }) /** diff --git a/packages/python-sdk/tests/shared/template/fixtures/hello.txt b/packages/python-sdk/tests/shared/template/fixtures/hello.txt new file mode 100644 index 0000000000..2caac9087c --- /dev/null +++ b/packages/python-sdk/tests/shared/template/fixtures/hello.txt @@ -0,0 +1,3 @@ +This file is the fixture for the implicit file-context test. It is read-only: +the implicit context is the directory of the file that calls Template(), which +is inside the repository, so nothing may be written here at test time. diff --git a/packages/python-sdk/tests/shared/template/test_file_context.py b/packages/python-sdk/tests/shared/template/test_file_context.py new file mode 100644 index 0000000000..4f68c3ab1d --- /dev/null +++ b/packages/python-sdk/tests/shared/template/test_file_context.py @@ -0,0 +1,44 @@ +import os +import tempfile + +import pytest + +from e2b import Template + +# Path to a committed, read-only fixture, relative to this file's directory. +# The implicit file context is the directory of the file that calls Template(), +# so it is always inside the repository — which is why the fixture is committed +# and never written to, rather than generated into a temp directory. +FIXTURE_PATH = "fixtures/hello.txt" + + +def test_file_context_defaults_to_caller_directory(): + implicit = Template().from_base_image().copy(FIXTURE_PATH, "hello.txt") + explicit = ( + Template(file_context_path=os.path.dirname(__file__)) + .from_base_image() + .copy(FIXTURE_PATH, "hello.txt") + ) + + # to_json hashes each COPY's files, so the two serializations only match if + # the implicit context resolved to this file's directory, the glob found the + # fixture there, and its contents were read. This is the only test that + # exercises the implicit default end to end — every other template test + # passes file_context_path explicitly, and the unit test for + # get_caller_directory covers the helper in isolation, not its use here. + assert Template.to_json(implicit) == Template.to_json(explicit) + + +def test_file_context_without_the_source_fails_to_resolve_it(): + # Keeps the assertion above from passing vacuously: the hashes match because + # the fixture was found, not because a missing file hashes the same either + # way. + with tempfile.TemporaryDirectory() as empty_context: + wrong_context = ( + Template(file_context_path=empty_context) + .from_base_image() + .copy(FIXTURE_PATH, "hello.txt") + ) + + with pytest.raises(ValueError, match="No files found"): + Template.to_json(wrong_context)