local createLibTest = require('/apis/libtest'); local testlib = createLibTest({ ... }); local function containsLine(lines, expected) for _, line in ipairs(lines) do if line == expected then return true; end end return false; end local function containsText(lines, expected) for _, line in ipairs(lines) do if string.find(line, expected, 1, true) then return true; end end return false; end local function fakeSettings(values) return { values = values or {}, saveCount = 0, get = function(self, key) return self.values[key]; end, set = function(self, key, value) self.values[key] = value; end, save = function(self) self.saveCount = self.saveCount + 1; return true; end, }; end local function runCloud(args, opts) opts = opts or {}; local lines = {}; local writes = {}; local readMasks = {}; local cloudRequests = {}; local queuedEvents = {}; local settingsLib = opts.settings or fakeSettings(); local readValues = opts.readValues or {}; local env = setmetatable({ print = function(line) lines[#lines + 1] = line or ''; end, write = function(text) writes[#writes + 1] = text or ''; end, read = function(mask) readMasks[#readMasks + 1] = mask; local value = readValues[#readMasks]; if value == nil then return ''; end return value; end, settings = { get = function(key) return settingsLib:get(key); end, set = function(key, value) return settingsLib:set(key, value); end, save = function() return settingsLib:save(); end, }, os = setmetatable({ queueEvent = function(...) queuedEvents[#queuedEvents + 1] = table.pack(...); end, }, { __index = os }), require = function(path) if path == '/apis/libcloud' then return function() return { request = function(msgType, payload, requestOpts) cloudRequests[#cloudRequests + 1] = { msgType = msgType, payload = payload, opts = requestOpts, }; return opts.requestOk ~= false, opts.requestResult or { serverOk = true, opencodeOk = true }; end, }; end; end if path == '/apis/libversion' then return function() return { forSelf = function() return '0.0.0-test'; end }; end; end return require(path); end, }, { __index = _G }); local chunk, loadErr = loadfile('/programs/cloud.lua', 't', env); if not chunk then error(loadErr, 0); end local ok, err = pcall(chunk, table.unpack(args or {})); if not ok then error(err, 0); end return { lines = lines, writes = writes, readMasks = readMasks, cloudRequests = cloudRequests, queuedEvents = queuedEvents, settings = settingsLib, }; end testlib.test('cloud help lists status and login without health', function() local ctx = runCloud({ '--help' }); testlib.assertTrue(containsLine(ctx.lines, ' cloud status')); testlib.assertTrue(containsLine(ctx.lines, ' cloud login [--force|-f] [password]')); testlib.assertTrue(not containsLine(ctx.lines, ' cloud health')); testlib.assertTrue(not containsText(ctx.lines, 'set-password')); end); testlib.test('cloud health is not kept as an alias', function() local ctx = runCloud({ 'health' }, { settings = fakeSettings({ ['cloud.url'] = 'wss://example.test' }), }); testlib.assertEquals(#ctx.cloudRequests, 0); testlib.assertTrue(containsLine(ctx.lines, 'cloud usage:')); end); testlib.test('cloud status probes the server', function() local ctx = runCloud({ 'status' }, { settings = fakeSettings({ ['cloud.url'] = 'wss://example.test' }), requestResult = { serverOk = true, opencodeOk = false, opencodeDetail = 'missing key' }, }); testlib.assertEquals(#ctx.cloudRequests, 1); testlib.assertEquals(ctx.cloudRequests[1].msgType, 'probe_server'); testlib.assertTrue(containsLine(ctx.lines, 'serverOk: true')); testlib.assertTrue(containsLine(ctx.lines, 'opencodeOk: false')); testlib.assertTrue(containsLine(ctx.lines, 'opencode: missing key')); end); testlib.test('cloud set-password is not kept as an alias', function() local ctx = runCloud({ 'set-password', 'hunter2' }); testlib.assertEquals(ctx.settings.values['cloud.password'], nil); testlib.assertEquals(ctx.settings.saveCount, 0); testlib.assertTrue(containsLine(ctx.lines, 'cloud usage:')); end); testlib.test('cloud login stores a missing password with masked input', function() local settingsLib = fakeSettings(); local ctx = runCloud({ 'login' }, { settings = settingsLib, readValues = { 'hunter2' }, }); testlib.assertEquals(ctx.writes[1], 'Password: '); testlib.assertEquals(ctx.readMasks[1], '*'); testlib.assertEquals(settingsLib.values['cloud.password'], 'hunter2'); testlib.assertEquals(settingsLib.saveCount, 1); testlib.assertEquals(ctx.queuedEvents[1][1], 'trapos_cloud_reconnect'); testlib.assertTrue(containsLine(ctx.lines, 'cloud.password set')); end); testlib.test('cloud login stores a password argument without prompting', function() local settingsLib = fakeSettings(); local ctx = runCloud({ 'login', 'hunter2' }, { settings = settingsLib, readValues = { 'unused' }, }); testlib.assertEquals(#ctx.readMasks, 0); testlib.assertEquals(settingsLib.values['cloud.password'], 'hunter2'); testlib.assertEquals(settingsLib.saveCount, 1); testlib.assertEquals(ctx.queuedEvents[1][1], 'trapos_cloud_reconnect'); end); testlib.test('cloud login rejects replacing without force', function() local settingsLib = fakeSettings({ ['cloud.password'] = 'old' }); local ctx = runCloud({ 'login' }, { settings = settingsLib, readValues = { 'new' }, }); testlib.assertEquals(#ctx.readMasks, 0); testlib.assertEquals(settingsLib.values['cloud.password'], 'old'); testlib.assertEquals(settingsLib.saveCount, 0); testlib.assertEquals(#ctx.queuedEvents, 0); testlib.assertTrue(containsText(ctx.lines, 'use cloud login --force')); end); testlib.test('cloud login --force replaces an existing password', function() local settingsLib = fakeSettings({ ['cloud.password'] = 'old' }); local ctx = runCloud({ 'login', '--force' }, { settings = settingsLib, readValues = { 'new' }, }); testlib.assertEquals(settingsLib.values['cloud.password'], 'new'); testlib.assertEquals(settingsLib.saveCount, 1); testlib.assertEquals(ctx.queuedEvents[1][1], 'trapos_cloud_reconnect'); end); testlib.test('cloud login -f replaces an existing password from argument', function() local settingsLib = fakeSettings({ ['cloud.password'] = 'old' }); local ctx = runCloud({ 'login', '-f', 'new' }, { settings = settingsLib, }); testlib.assertEquals(#ctx.readMasks, 0); testlib.assertEquals(settingsLib.values['cloud.password'], 'new'); testlib.assertEquals(settingsLib.saveCount, 1); testlib.assertEquals(ctx.queuedEvents[1][1], 'trapos_cloud_reconnect'); end); testlib.test('cloud login rejects an empty password', function() local settingsLib = fakeSettings(); local ctx = runCloud({ 'login' }, { settings = settingsLib, readValues = { '' }, }); testlib.assertEquals(settingsLib.values['cloud.password'], nil); testlib.assertEquals(settingsLib.saveCount, 0); testlib.assertEquals(#ctx.queuedEvents, 0); testlib.assertTrue(containsLine(ctx.lines, 'cloud.password not changed: password cannot be empty')); end); testlib.run();