package tech.easyflow.agent.runtime.workspace;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import tech.easyflow.agent.config.AgentWorkspaceProperties;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;

/**
 * Agent 会话工作区隔离和路径越界防护测试。
 */
public class AgentWorkspaceResolverTest {

    /** 临时工作区根目录。 */
    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    /**
     * 验证工作区按 tenant、agent、runtimeSession 三级隔离。
     *
     * @throws Exception 临时目录创建失败
     */
    @Test
    public void resolveShouldCreateThreeLevelIsolatedWorkspace() throws Exception {
        AgentWorkspaceResolver resolver = resolver();

        Path workspace = resolver.resolve(BigInteger.ONE, BigInteger.TWO, "runtime-3");

        Assert.assertEquals("1/2/runtime-3", resolver.getRealRoot().relativize(workspace).toString());
        Assert.assertTrue(Files.isDirectory(workspace));
        Assert.assertTrue(Files.exists(resolver.activityFile(workspace)));
    }

    /**
     * 验证 Artifact 读取不能通过父路径或符号链接逃逸工作区。
     *
     * @throws Exception 测试文件创建失败
     */
    @Test
    public void resolveExistingFileShouldRejectTraversalAndSymlink() throws Exception {
        AgentWorkspaceResolver resolver = resolver();
        Path workspace = resolver.resolve(BigInteger.ONE, BigInteger.TWO, "runtime-3");
        Path outside = temporaryFolder.newFile("outside.txt").toPath();
        Files.createSymbolicLink(workspace.resolve("escape.txt"), outside);

        assertForbidden(resolver, workspace, "../outside.txt");
        assertForbidden(resolver, workspace, "escape.txt");
    }

    /**
     * 验证 Unix 硬链接不能绕过工作区真实文件归属检查。
     *
     * @throws Exception 测试文件或硬链接创建失败
     */
    @Test
    public void resolveExistingFileShouldRejectUnixHardlink() throws Exception {
        AgentWorkspaceResolver resolver = resolver();
        Path workspace = resolver.resolve(BigInteger.ONE, BigInteger.TWO, "runtime-3");
        Path outside = temporaryFolder.newFile("hardlink-source.txt").toPath();
        Files.createLink(workspace.resolve("hardlink.txt"), outside);

        assertForbidden(resolver, workspace, "hardlink.txt");
    }

    private AgentWorkspaceResolver resolver() throws Exception {
        AgentWorkspaceProperties properties = new AgentWorkspaceProperties();
        properties.setRoot(temporaryFolder.newFolder("agent-workspaces").getAbsolutePath());
        AgentWorkspaceResolver resolver = new AgentWorkspaceResolver(properties);
        resolver.initialize();
        return resolver;
    }

    private void assertForbidden(AgentWorkspaceResolver resolver, Path workspace, String path) {
        try {
            resolver.resolveExistingFile(workspace, path);
            Assert.fail("Expected BusinessException");
        } catch (BusinessException expected) {
            Assert.assertNotNull(expected.getMessage());
        }
    }
}
